Files
napi-go/internal/napi/instance_data.go
Matheus Sampaio Queiroga 652638e8ee feat: Add AsyncWork and Promise support
Introduces `napi.CreateAyncWorker` for background tasks and
`napi.CreatePromise` for handling asynchronous operations via deferred
objects.

Refactors the module export registration to use `go:linkname`,
replacing the previous `entry.Register` function. Adds new examples
demonstrating these features and the updated export style.
2025-04-27 19:25:52 -03:00

392 lines
9.1 KiB
Go

package napi
/*
#include <stdlib.h>
#include <node/node_api.h>
extern void DeleteInstanceData(
napi_env env,
void *finalize_data,
void *finalize_hint
);
extern void DeleteCallbackData(
napi_env env,
void *finalize_data,
void *finalize_hint
);
extern napi_value ExecuteCallback(
napi_env env,
napi_callback_info info
);
extern void ExecuteAsyncExecuteCallback(
napi_env env,
void *data
);
extern void ExecuteAsyncCompleteCallback(
napi_env env,
napi_status status,
void *data
);
*/
import "C"
import (
"fmt"
"runtime/cgo"
"sync"
"unsafe"
)
type NapiGoInstanceData struct {
UserData any
CallbackData NapiGoInstanceCallbackData
AsyncWorkData NapiGoInstanceAsyncWorkData
}
type NapiGoInstanceCallbackData struct {
CallbackMap NapiGoInstanceCallbackMap
NextID NapiGoCallbackID
Lock sync.RWMutex
}
type NapiGoCallbackID int
type NapiGoInstanceCallbackMap map[NapiGoCallbackID]*NapiGoCallbackMapEntry
type NapiGoCallbackMapEntry struct {
Callback Callback
ID NapiGoCallbackID
}
type NapiGoAsyncWorkID int
type NapiGoInstanceAsyncWorkData struct {
AsyncWorkMap NapiGoInstanceAsyncWorkMap
NextID NapiGoAsyncWorkID
Lock sync.RWMutex
}
type NapiGoInstanceAsyncWorkMap map[NapiGoAsyncWorkID]*NapiGoAsyncWorkMapEntry
type NapiGoAsyncWorkMapEntry struct {
Execute AsyncExecuteCallback
Complete AsyncCompleteCallback
ID NapiGoAsyncWorkID
}
type InstanceDataProvider interface {
GetUserData() any
SetUserData(userData any)
GetCallbackData() CallbackDataProvider
GetAsyncWorkData() AsyncWorkDataProvider
}
type CallbackDataProvider interface {
CreateCallback(env Env, name string, cb Callback) (Value, Status)
GetCallback(id NapiGoCallbackID) *NapiGoCallbackMapEntry
DeleteCallback(id NapiGoCallbackID)
}
type AsyncWorkDataProvider interface {
CreateAsyncWork(env Env, asyncResource, asyncResourceName Value, execute AsyncExecuteCallback, complete AsyncCompleteCallback) (AsyncWork, Status)
GetAsyncWork(id NapiGoAsyncWorkID) *NapiGoAsyncWorkMapEntry
DeleteAsyncWork(id NapiGoAsyncWorkID)
}
var _ InstanceDataProvider = &NapiGoInstanceData{}
var _ CallbackDataProvider = &NapiGoInstanceCallbackData{}
var _ AsyncWorkDataProvider = &NapiGoInstanceAsyncWorkData{}
const maxStackTraceSize = 8192
func InitializeInstanceData(env Env) Status {
return setInstanceData(env, &NapiGoInstanceData{})
}
//export DeleteInstanceData
func DeleteInstanceData(env C.napi_env, finalizeData, finalizeHint unsafe.Pointer) {
instanceDataHandle := cgo.Handle(finalizeData)
instanceDataHandle.Delete()
}
//export DeleteCallbackData
func DeleteCallbackData(cEnv C.napi_env, finalizeData, finalizeHint unsafe.Pointer) {
env := Env(cEnv)
defer func() {
err := recover()
if err != nil {
ThrowError(env, "", fmt.Sprintf("napi.DeleteCallbackData: Recovered from panic: %s\n", err))
}
}()
instanceData, status := getInstanceData(env)
if status != StatusOK {
panic(StatusError(status))
}
id := *(*NapiGoCallbackID)(finalizeData)
instanceData.GetCallbackData().DeleteCallback(id)
}
//export ExecuteCallback
func ExecuteCallback(cEnv C.napi_env, cInfo C.napi_callback_info) C.napi_value {
env := Env(cEnv)
defer func() {
err := recover()
if err != nil {
ThrowError(env, "", fmt.Sprintf("napi.ExecuteCallback: Recovered from panic: %s\n", err))
}
}()
instanceData, status := getInstanceData(env)
if status != StatusOK {
panic(StatusError(status))
}
argc := C.size_t(0)
var cData unsafe.Pointer
status = Status(C.napi_get_cb_info(
cEnv,
cInfo,
&argc,
nil,
nil,
&cData,
))
if status != StatusOK {
panic(StatusError(status))
}
id := *(*NapiGoCallbackID)(cData)
callbackData := instanceData.GetCallbackData().GetCallback(id)
info := CallbackInfo(cInfo)
result := callbackData.Callback(env, info)
return C.napi_value(result)
}
//export ExecuteAsyncExecuteCallback
func ExecuteAsyncExecuteCallback(cEnv C.napi_env, cData unsafe.Pointer) {
env := Env(cEnv)
defer func() {
err := recover()
if err != nil {
ThrowError(env, "", fmt.Sprintf("napi.ExecuteAsyncExecuteCallback: Recovered from panic: %s\n", err))
}
}()
instanceData, status := getInstanceData(env)
if status != StatusOK {
panic(StatusError(status))
}
id := *(*NapiGoAsyncWorkID)(cData)
asyncWorkData := instanceData.GetAsyncWorkData().GetAsyncWork(id)
asyncWorkData.Execute(env)
}
//export ExecuteAsyncCompleteCallback
func ExecuteAsyncCompleteCallback(cEnv C.napi_env, cStatus C.napi_status, cData unsafe.Pointer) {
env := Env(cEnv)
defer func() {
err := recover()
if err != nil {
ThrowError(env, "", fmt.Sprintf("napi.ExecuteAsyncExecuteCallback: Recovered from panic: %s", err))
}
}()
instanceData, status := getInstanceData(env)
if status != StatusOK {
panic(StatusError(status))
}
id := *(*NapiGoAsyncWorkID)(cData)
asyncWorkData := instanceData.GetAsyncWorkData().GetAsyncWork(id)
asyncWorkData.Complete(env, Status(cStatus))
}
func getInstanceDataHandle(env Env) (cgo.Handle, Status) {
var result unsafe.Pointer
status := Status(C.napi_get_instance_data(C.napi_env(env), &result))
if status != StatusOK || result == nil {
return cgo.Handle(0), status
}
return cgo.Handle(result), status
}
func getInstanceData(env Env) (InstanceDataProvider, Status) {
handle, status := getInstanceDataHandle(env)
if status != StatusOK || handle == 0 {
return nil, status
}
return handle.Value().(InstanceDataProvider), status
}
func setInstanceData(env Env, data *NapiGoInstanceData) Status {
// check if an existing handle is already set, and clean it up if so
// (napi won't invoke the finalizer if overwriting instance data)
handle, status := getInstanceDataHandle(env)
if status != StatusOK {
return status
}
if handle != 0 {
handle.Delete()
}
dataHandle := cgo.NewHandle(data)
return Status(C.napi_set_instance_data(
C.napi_env(env),
unsafe.Pointer(dataHandle),
C.napi_finalize(C.DeleteInstanceData),
nil,
))
}
func (d *NapiGoInstanceData) GetUserData() any {
return d.UserData
}
func (d *NapiGoInstanceData) SetUserData(userData any) {
d.UserData = userData
}
func (d *NapiGoInstanceData) GetCallbackData() CallbackDataProvider {
return &d.CallbackData
}
func (d *NapiGoInstanceData) GetAsyncWorkData() AsyncWorkDataProvider {
return &d.AsyncWorkData
}
func (d *NapiGoInstanceCallbackData) CreateCallback(env Env, name string, cb Callback) (Value, Status) {
d.Lock.Lock()
defer d.Lock.Unlock()
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
callbackState := d.insert(cb)
var result Value
status := Status(C.napi_create_function(
C.napi_env(env),
cname,
C.size_t(len([]byte(name))),
C.napi_callback(C.ExecuteCallback),
unsafe.Pointer(&callbackState.ID),
(*C.napi_value)(unsafe.Pointer(&result)),
))
if status == StatusOK {
status = Status(C.napi_add_finalizer(
C.napi_env(env),
C.napi_value(result),
unsafe.Pointer(&callbackState.ID),
C.napi_finalize(C.DeleteCallbackData),
nil,
nil,
))
}
return result, status
}
func (d *NapiGoInstanceCallbackData) GetCallback(id NapiGoCallbackID) *NapiGoCallbackMapEntry {
d.Lock.RLock()
defer d.Lock.RUnlock()
return d.CallbackMap[id]
}
func (d *NapiGoInstanceCallbackData) DeleteCallback(id NapiGoCallbackID) {
d.Lock.Lock()
defer d.Lock.Unlock()
delete(d.CallbackMap, id)
}
func (d *NapiGoInstanceCallbackData) insert(cb Callback) *NapiGoCallbackMapEntry {
// callers are expected to lock
if d.CallbackMap == nil {
d.CallbackMap = NapiGoInstanceCallbackMap{}
}
for {
id := d.NextID
d.NextID++
if d.CallbackMap[id] == nil {
result := &NapiGoCallbackMapEntry{
Callback: cb,
ID: id,
}
d.CallbackMap[id] = result
return result
}
}
}
func (d *NapiGoInstanceAsyncWorkData) CreateAsyncWork(env Env, asyncResource, asyncResourceName Value, execute AsyncExecuteCallback, complete AsyncCompleteCallback) (AsyncWork, Status) {
d.Lock.Lock()
defer d.Lock.Unlock()
asyncWorkState := d.insert(execute, complete)
result := AsyncWork{
ID: asyncWorkState.ID,
}
status := Status(C.napi_create_async_work(
C.napi_env(env),
C.napi_value(asyncResource),
C.napi_value(asyncResourceName),
C.napi_async_execute_callback(C.ExecuteAsyncExecuteCallback),
C.napi_async_complete_callback(C.ExecuteAsyncCompleteCallback),
unsafe.Pointer(&asyncWorkState.ID),
(*C.napi_async_work)(unsafe.Pointer(&result.Handle)),
))
return result, status
}
func (d *NapiGoInstanceAsyncWorkData) GetAsyncWork(id NapiGoAsyncWorkID) *NapiGoAsyncWorkMapEntry {
d.Lock.RLock()
defer d.Lock.RUnlock()
return d.AsyncWorkMap[id]
}
func (d *NapiGoInstanceAsyncWorkData) DeleteAsyncWork(id NapiGoAsyncWorkID) {
d.Lock.Lock()
defer d.Lock.Unlock()
delete(d.AsyncWorkMap, id)
}
func (d *NapiGoInstanceAsyncWorkData) insert(execute AsyncExecuteCallback, complete AsyncCompleteCallback) *NapiGoAsyncWorkMapEntry {
// callers are expected to lock
if d.AsyncWorkMap == nil {
d.AsyncWorkMap = NapiGoInstanceAsyncWorkMap{}
}
for {
id := d.NextID
d.NextID++
if d.AsyncWorkMap[id] == nil {
result := &NapiGoAsyncWorkMapEntry{
Execute: execute,
Complete: complete,
ID: id,
}
d.AsyncWorkMap[id] = result
return result
}
}
}