removing old go wrappers

This commit is contained in:
Gabriel mermelstein 2025-04-16 17:54:48 +03:00
parent 415d0d8a42
commit d9c3bccc31
No known key found for this signature in database
GPG Key ID: 82B8134785FEAE0D
2 changed files with 0 additions and 540 deletions

View File

@ -1,281 +0,0 @@
package main
/*
#cgo CFLAGS: -I${SRCDIR}/library
#cgo LDFLAGS: -L${SRCDIR}/build -llibsds
#cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/build
#include <stdlib.h> // For C.free
#include "library/libsds.h" // Update include path
// Forward declaration for the single Go callback relay function
extern void globalCallbackRelay(void* handle, CEventType eventType, void* data1, void* data2, size_t data3);
// Helper function to call the C memory freeing functions
static void callFreeCResultError(CResult res) { FreeCResultError(res); }
static void callFreeCWrapResult(CWrapResult res) { FreeCWrapResult(res); }
static void callFreeCUnwrapResult(CUnwrapResult res) { FreeCUnwrapResult(res); }
*/
import "C"
import (
"errors"
"fmt"
"sync"
"unsafe"
)
// --- Go Types ---
// ReliabilityManagerHandle represents the opaque handle to the Nim object
type ReliabilityManagerHandle unsafe.Pointer
// MessageID is a type alias for string for clarity
type MessageID string
// Callbacks holds the Go functions to be called by the Nim library
type Callbacks struct {
OnMessageReady func(messageId MessageID)
OnMessageSent func(messageId MessageID)
OnMissingDependencies func(messageId MessageID, missingDeps []MessageID)
OnPeriodicSync func()
}
// Global map to store callbacks associated with handles
var (
callbackRegistry = make(map[ReliabilityManagerHandle]*Callbacks)
registryMutex sync.RWMutex
)
// --- Go Wrapper Functions ---
// NewReliabilityManager creates a new instance of the Nim ReliabilityManager
func NewReliabilityManager(channelId string) (ReliabilityManagerHandle, error) {
cChannelId := C.CString(channelId)
defer C.free(unsafe.Pointer(cChannelId))
handle := C.NewReliabilityManager(cChannelId)
if handle == nil {
// Note: Nim side currently just prints to stdout on creation failure
return nil, errors.New("failed to create ReliabilityManager (check Nim logs/stdout)")
}
return ReliabilityManagerHandle(handle), nil
}
// CleanupReliabilityManager frees the resources associated with the handle
func CleanupReliabilityManager(handle ReliabilityManagerHandle) {
if handle == nil {
return
}
registryMutex.Lock()
delete(callbackRegistry, handle)
registryMutex.Unlock()
C.CleanupReliabilityManager(unsafe.Pointer(handle))
}
// ResetReliabilityManager resets the state of the manager
func ResetReliabilityManager(handle ReliabilityManagerHandle) error {
if handle == nil {
return errors.New("handle is nil")
}
cResult := C.ResetReliabilityManager(unsafe.Pointer(handle))
if !cResult.is_ok {
errMsg := C.GoString(cResult.error_message)
C.callFreeCResultError(cResult) // Free the error message
return errors.New(errMsg)
}
return nil
}
// WrapOutgoingMessage wraps a message with reliability metadata
func WrapOutgoingMessage(handle ReliabilityManagerHandle, message []byte, messageId MessageID) ([]byte, error) {
if handle == nil {
return nil, errors.New("handle is nil")
}
cMessageId := C.CString(string(messageId))
defer C.free(unsafe.Pointer(cMessageId))
var cMessagePtr unsafe.Pointer
if len(message) > 0 {
cMessagePtr = C.CBytes(message) // C.CBytes allocates memory that needs to be freed
defer C.free(cMessagePtr)
} else {
cMessagePtr = nil
}
cMessageLen := C.size_t(len(message))
cWrapResult := C.WrapOutgoingMessage(unsafe.Pointer(handle), cMessagePtr, cMessageLen, cMessageId)
if !cWrapResult.base_result.is_ok {
errMsg := C.GoString(cWrapResult.base_result.error_message)
C.callFreeCWrapResult(cWrapResult) // Free error and potentially allocated message
return nil, errors.New(errMsg)
}
// Copy the wrapped message from C memory to Go slice
// Explicitly cast the message pointer to unsafe.Pointer
wrappedMessage := C.GoBytes(unsafe.Pointer(cWrapResult.message), C.int(cWrapResult.message_len))
C.callFreeCWrapResult(cWrapResult) // Free the C-allocated message buffer
return wrappedMessage, nil
}
// UnwrapReceivedMessage unwraps a received message
func UnwrapReceivedMessage(handle ReliabilityManagerHandle, message []byte) ([]byte, []MessageID, error) {
if handle == nil {
return nil, nil, errors.New("handle is nil")
}
var cMessagePtr unsafe.Pointer
if len(message) > 0 {
cMessagePtr = C.CBytes(message)
defer C.free(cMessagePtr)
} else {
cMessagePtr = nil
}
cMessageLen := C.size_t(len(message))
cUnwrapResult := C.UnwrapReceivedMessage(unsafe.Pointer(handle), cMessagePtr, cMessageLen)
if !cUnwrapResult.base_result.is_ok {
errMsg := C.GoString(cUnwrapResult.base_result.error_message)
C.callFreeCUnwrapResult(cUnwrapResult) // Free error and potentially allocated fields
return nil, nil, errors.New(errMsg)
}
// Copy unwrapped message content
unwrappedContent := C.GoBytes(unsafe.Pointer(cUnwrapResult.message), C.int(cUnwrapResult.message_len))
// Copy missing dependencies
missingDeps := make([]MessageID, cUnwrapResult.missing_deps_count)
if cUnwrapResult.missing_deps_count > 0 {
// Convert C array of C strings to Go slice of strings
cDepsArray := (*[1 << 30]*C.char)(unsafe.Pointer(cUnwrapResult.missing_deps))[:cUnwrapResult.missing_deps_count:cUnwrapResult.missing_deps_count]
for i, s := range cDepsArray {
missingDeps[i] = MessageID(C.GoString(s))
}
}
C.callFreeCUnwrapResult(cUnwrapResult) // Free C-allocated message, deps array, and strings
return unwrappedContent, missingDeps, nil
}
// MarkDependenciesMet informs the library that dependencies are met
func MarkDependenciesMet(handle ReliabilityManagerHandle, messageIDs []MessageID) error {
if handle == nil {
return errors.New("handle is nil")
}
if len(messageIDs) == 0 {
return nil // Nothing to do
}
// Convert Go string slice to C array of C strings (char**)
cMessageIDs := make([]*C.char, len(messageIDs))
for i, id := range messageIDs {
cMessageIDs[i] = C.CString(string(id))
defer C.free(unsafe.Pointer(cMessageIDs[i])) // Ensure each CString is freed
}
// Create a pointer (**C.char) to the first element of the slice
var cMessageIDsPtr **C.char
if len(cMessageIDs) > 0 {
cMessageIDsPtr = &cMessageIDs[0]
} else {
cMessageIDsPtr = nil // Handle empty slice case
}
// Pass the pointer variable (cMessageIDsPtr) directly, which is of type **C.char
cResult := C.MarkDependenciesMet(unsafe.Pointer(handle), cMessageIDsPtr, C.size_t(len(messageIDs)))
if !cResult.is_ok {
errMsg := C.GoString(cResult.error_message)
C.callFreeCResultError(cResult)
return errors.New(errMsg)
}
return nil
}
// RegisterCallback sets the single Go callback relay function
func RegisterCallback(handle ReliabilityManagerHandle, callbacks Callbacks) error {
if handle == nil {
return errors.New("handle is nil")
}
// Store the Go callbacks associated with this handle
registryMutex.Lock()
callbackRegistry[handle] = &callbacks
registryMutex.Unlock()
// Register the single global Go relay function with the Nim library
// Nim will call globalCallbackRelay, passing the handle as the first argument.
C.RegisterCallback(
unsafe.Pointer(handle),
(C.CEventCallback)(C.globalCallbackRelay), // Pass the Go relay function pointer
nil, // user_data is not used here, handle is passed directly by Nim wrapper
)
return nil
}
// StartPeriodicTasks starts the background tasks in the Nim library
func StartPeriodicTasks(handle ReliabilityManagerHandle) error {
if handle == nil {
return errors.New("handle is nil")
}
C.StartPeriodicTasks(unsafe.Pointer(handle))
// Assuming StartPeriodicTasks doesn't return an error status in C API
return nil
}
// globalCallbackRelay is called by Nim for all events.
// It uses the handle to find the correct Go Callbacks struct and dispatch the call.
//
//export globalCallbackRelay
func globalCallbackRelay(handle unsafe.Pointer, eventType C.CEventType, data1 unsafe.Pointer, data2 unsafe.Pointer, data3 C.size_t) {
goHandle := ReliabilityManagerHandle(handle)
registryMutex.RLock()
callbacks, ok := callbackRegistry[goHandle]
registryMutex.RUnlock()
if !ok || callbacks == nil {
fmt.Printf("Go: globalCallbackRelay: No callbacks registered for handle %v\n", goHandle) // Uncommented
return
}
// Use a goroutine to avoid blocking the Nim thread
go func() {
switch eventType {
case C.EVENT_MESSAGE_READY:
if callbacks.OnMessageReady != nil {
msgIdStr := C.GoString((*C.char)(data1))
callbacks.OnMessageReady(MessageID(msgIdStr))
}
case C.EVENT_MESSAGE_SENT:
if callbacks.OnMessageSent != nil {
msgIdStr := C.GoString((*C.char)(data1))
callbacks.OnMessageSent(MessageID(msgIdStr))
}
case C.EVENT_MISSING_DEPENDENCIES:
if callbacks.OnMissingDependencies != nil {
msgIdStr := C.GoString((*C.char)(data1))
depsCount := int(data3)
deps := make([]MessageID, depsCount)
if depsCount > 0 {
// Convert C array of C strings (**char) to Go slice
cDepsArray := (*[1 << 30]*C.char)(data2)[:depsCount:depsCount]
for i, s := range cDepsArray {
deps[i] = MessageID(C.GoString(s))
}
}
callbacks.OnMissingDependencies(MessageID(msgIdStr), deps)
}
case C.EVENT_PERIODIC_SYNC:
if callbacks.OnPeriodicSync != nil {
callbacks.OnPeriodicSync()
}
default:
fmt.Printf("Go: globalCallbackRelay: Received unknown event type %d for handle %v\n", eventType, goHandle)
}
}()
}

View File

@ -1,259 +0,0 @@
package main
import (
"fmt"
"sync"
"testing"
"time"
)
// Test basic creation, cleanup, and reset
func TestLifecycle(t *testing.T) {
channelID := "test-lifecycle"
handle, err := NewReliabilityManager(channelID)
if err != nil {
t.Fatalf("NewReliabilityManager failed: %v", err)
}
if handle == nil {
t.Fatal("NewReliabilityManager returned a nil handle")
}
defer CleanupReliabilityManager(handle) // Ensure cleanup even on test failure
err = ResetReliabilityManager(handle)
if err != nil {
t.Errorf("ResetReliabilityManager failed: %v", err)
}
}
// Test wrapping and unwrapping a simple message
func TestWrapUnwrap(t *testing.T) {
channelID := "test-wrap-unwrap"
handle, err := NewReliabilityManager(channelID)
if err != nil {
t.Fatalf("NewReliabilityManager failed: %v", err)
}
defer CleanupReliabilityManager(handle)
originalPayload := []byte("hello reliability")
messageID := MessageID("msg-wrap-1")
wrappedMsg, err := WrapOutgoingMessage(handle, originalPayload, messageID)
if err != nil {
t.Fatalf("WrapOutgoingMessage failed: %v", err)
}
if len(wrappedMsg) == 0 {
t.Fatal("WrapOutgoingMessage returned empty bytes")
}
// Simulate receiving the wrapped message
unwrappedPayload, missingDeps, err := UnwrapReceivedMessage(handle, wrappedMsg)
if err != nil {
t.Fatalf("UnwrapReceivedMessage failed: %v", err)
}
if string(unwrappedPayload) != string(originalPayload) {
t.Errorf("Unwrapped payload mismatch: got %q, want %q", unwrappedPayload, originalPayload)
}
if len(missingDeps) != 0 {
t.Errorf("Expected 0 missing dependencies, got %d: %v", len(missingDeps), missingDeps)
}
}
// Test dependency handling
func TestDependencies(t *testing.T) {
channelID := "test-deps"
handle, err := NewReliabilityManager(channelID)
if err != nil {
t.Fatalf("NewReliabilityManager failed: %v", err)
}
defer CleanupReliabilityManager(handle)
// 1. Send message 1 (will become a dependency)
payload1 := []byte("message one")
msgID1 := MessageID("msg-dep-1")
wrappedMsg1, err := WrapOutgoingMessage(handle, payload1, msgID1)
if err != nil {
t.Fatalf("WrapOutgoingMessage (1) failed: %v", err)
}
// Simulate receiving msg1 to add it to history (implicitly acknowledges it)
_, _, err = UnwrapReceivedMessage(handle, wrappedMsg1)
if err != nil {
t.Fatalf("UnwrapReceivedMessage (1) failed: %v", err)
}
// 2. Send message 2 (depends on message 1 implicitly via causal history)
payload2 := []byte("message two")
msgID2 := MessageID("msg-dep-2")
wrappedMsg2, err := WrapOutgoingMessage(handle, payload2, msgID2)
if err != nil {
t.Fatalf("WrapOutgoingMessage (2) failed: %v", err)
}
// 3. Create a new manager to simulate a different peer receiving msg2 without msg1
handle2, err := NewReliabilityManager(channelID) // Same channel ID
if err != nil {
t.Fatalf("NewReliabilityManager (2) failed: %v", err)
}
defer CleanupReliabilityManager(handle2)
// 4. Unwrap message 2 on the second manager - should report msg1 as missing
_, missingDeps, err := UnwrapReceivedMessage(handle2, wrappedMsg2)
if err != nil {
t.Fatalf("UnwrapReceivedMessage (2) on handle2 failed: %v", err)
}
if len(missingDeps) == 0 {
t.Fatalf("Expected missing dependencies, got none")
}
foundDep1 := false
for _, dep := range missingDeps {
if dep == msgID1 {
foundDep1 = true
break
}
}
if !foundDep1 {
t.Errorf("Expected missing dependency %q, got %v", msgID1, missingDeps)
}
// 5. Mark the dependency as met
err = MarkDependenciesMet(handle2, []MessageID{msgID1})
if err != nil {
t.Fatalf("MarkDependenciesMet failed: %v", err)
}
}
// Test callbacks
func TestCallbacks(t *testing.T) {
channelID := "test-callbacks"
handle, err := NewReliabilityManager(channelID)
if err != nil {
t.Fatalf("NewReliabilityManager failed: %v", err)
}
defer CleanupReliabilityManager(handle)
var wg sync.WaitGroup
receivedReady := make(map[MessageID]bool)
receivedSent := make(map[MessageID]bool)
receivedMissing := make(map[MessageID][]MessageID)
syncRequested := false
var cbMutex sync.Mutex // Protect access to callback tracking maps/vars
callbacks := Callbacks{
OnMessageReady: func(messageId MessageID) {
fmt.Printf("Test: OnMessageReady received: %s\n", messageId)
cbMutex.Lock()
receivedReady[messageId] = true
cbMutex.Unlock()
wg.Done()
},
OnMessageSent: func(messageId MessageID) {
fmt.Printf("Test: OnMessageSent received: %s\n", messageId)
cbMutex.Lock()
receivedSent[messageId] = true
cbMutex.Unlock()
wg.Done()
},
OnMissingDependencies: func(messageId MessageID, missingDeps []MessageID) {
fmt.Printf("Test: OnMissingDependencies received for %s: %v\n", messageId, missingDeps)
cbMutex.Lock()
receivedMissing[messageId] = missingDeps
cbMutex.Unlock()
wg.Done()
},
OnPeriodicSync: func() {
fmt.Println("Test: OnPeriodicSync received")
cbMutex.Lock()
syncRequested = true
cbMutex.Unlock()
// Don't wg.Done() here, it might be called multiple times
},
}
err = RegisterCallback(handle, callbacks)
if err != nil {
t.Fatalf("RegisterCallback failed: %v", err)
}
// Start tasks AFTER registering callbacks
err = StartPeriodicTasks(handle)
if err != nil {
t.Fatalf("StartPeriodicTasks failed: %v", err)
}
// --- Test Scenario ---
// 1. Send msg1
wg.Add(1) // Expect OnMessageSent for msg1 eventually
payload1 := []byte("callback test 1")
msgID1 := MessageID("cb-msg-1")
wrappedMsg1, err := WrapOutgoingMessage(handle, payload1, msgID1)
if err != nil {
t.Fatalf("WrapOutgoingMessage (1) failed: %v", err)
}
// 2. Receive msg1 (triggers OnMessageReady for msg1, OnMessageSent for msg1 via causal history)
wg.Add(1) // Expect OnMessageReady for msg1
_, _, err = UnwrapReceivedMessage(handle, wrappedMsg1)
if err != nil {
t.Fatalf("UnwrapReceivedMessage (1) failed: %v", err)
}
// 3. Send msg2 (depends on msg1)
wg.Add(1) // Expect OnMessageSent for msg2 eventually
payload2 := []byte("callback test 2")
msgID2 := MessageID("cb-msg-2")
wrappedMsg2, err := WrapOutgoingMessage(handle, payload2, msgID2)
if err != nil {
t.Fatalf("WrapOutgoingMessage (2) failed: %v", err)
}
// 4. Receive msg2 (triggers OnMessageReady for msg2, OnMessageSent for msg2)
wg.Add(1) // Expect OnMessageReady for msg2
_, _, err = UnwrapReceivedMessage(handle, wrappedMsg2)
if err != nil {
t.Fatalf("UnwrapReceivedMessage (2) failed: %v", err)
}
// --- Verification ---
// Wait for expected callbacks with a timeout
waitTimeout(&wg, 5*time.Second, t)
cbMutex.Lock()
defer cbMutex.Unlock()
if !receivedReady[msgID1] {
t.Errorf("OnMessageReady not called for %s", msgID1)
}
if !receivedReady[msgID2] {
t.Errorf("OnMessageReady not called for %s", msgID2)
}
if !receivedSent[msgID1] {
t.Errorf("OnMessageSent not called for %s", msgID1)
}
if !receivedSent[msgID2] {
t.Errorf("OnMessageSent not called for %s", msgID2)
}
// We didn't explicitly test missing deps in this path
if len(receivedMissing) > 0 {
t.Errorf("Unexpected OnMissingDependencies calls: %v", receivedMissing)
}
// Periodic sync is harder to guarantee in a short test, just check if it was ever true
if !syncRequested {
t.Logf("Warning: OnPeriodicSync might not have been called within the test timeout")
}
}
// Helper function to wait for WaitGroup with a timeout
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration, t *testing.T) {
c := make(chan struct{})
go func() {
defer close(c)
wg.Wait()
}()
select {
case <-c:
// Completed normally
case <-time.After(timeout):
t.Fatalf("Timed out waiting for callbacks")
}
}