blob: cab2b5459db3cdf66ec6d41578ccbc11a9ddda45 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package paimon
import (
"context"
"errors"
"os"
"sync/atomic"
"unsafe"
"github.com/jupiterrider/ffi"
)
// libRef is an atomic reference counter for the loaded shared library.
// Every object that may call FFI (including during Close/Release) must
// hold a reference. The library is freed only when the count drops to zero.
type libRef struct {
count atomic.Int32
lib uintptr
}
func newLibRef(lib uintptr) *libRef {
r := &libRef{lib: lib}
r.count.Store(1)
return r
}
func (r *libRef) acquire() { r.count.Add(1) }
func (r *libRef) release() {
if r.count.Add(-1) == 0 {
_ = freeLibrary(r.lib)
}
}
type ffiOpts struct {
sym contextKey
rType *ffi.Type
aTypes []*ffi.Type
}
type ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer)
type contextKey string
func (c contextKey) String() string {
return string(c)
}
type contextWithFFI func(ctx context.Context, lib uintptr) (context.Context, error)
// FFI is a generic type-safe wrapper for a foreign function.
type FFI[T any] struct {
opts ffiOpts
withFunc func(ctx context.Context, ffiCall ffiCall) T
}
func newFFI[T any](opts ffiOpts, withFunc func(ctx context.Context, ffiCall ffiCall) T) *FFI[T] {
f := &FFI[T]{
opts: opts,
withFunc: withFunc,
}
withFFIs = append(withFFIs, f.withFFI)
return f
}
func (f *FFI[T]) symbol(ctx context.Context) T {
return ctx.Value(f.opts.sym).(T)
}
func (f *FFI[T]) withFFI(ctx context.Context, lib uintptr) (context.Context, error) {
var cif ffi.Cif
if status := ffi.PrepCif(
&cif,
ffi.DefaultAbi,
uint32(len(f.opts.aTypes)),
f.opts.rType,
f.opts.aTypes...,
); status != ffi.OK {
return nil, errors.New(status.String())
}
fn, err := getProcAddress(lib, f.opts.sym.String())
if err != nil {
return nil, err
}
val := f.withFunc(ctx, func(rValue unsafe.Pointer, aValues ...unsafe.Pointer) {
ffi.Call(&cif, fn, rValue, aValues...)
})
return context.WithValue(ctx, f.opts.sym, val), nil
}
var withFFIs []contextWithFFI
func newContext(path string) (ctx context.Context, lib *libRef, err error) {
handle, err := loadLibrary(path)
if err != nil {
return
}
ctx = context.Background()
for _, withFFI := range withFFIs {
ctx, err = withFFI(ctx, handle)
if err != nil {
_ = freeLibrary(handle)
return
}
}
if removeErr := os.Remove(path); removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) {
_ = freeLibrary(handle)
err = removeErr
return
}
lib = newLibRef(handle)
return
}