nim-sds/library/libsds.nim

382 lines
12 KiB
Nim
Raw Normal View History

{.pragma: exported, exportc, cdecl, raises: [].}
{.pragma: callback, cdecl, raises: [], gcsafe.}
{.passc: "-fPIC".}
when defined(linux):
{.passl: "-Wl,-soname,libsds.so".}
2025-08-19 11:33:12 +05:30
import std/[typetraits, tables, atomics], chronos, chronicles, os, times, strutils
import
./sds_thread/sds_thread,
./alloc,
./ffi_types,
./sds_thread/inter_thread_communication/sds_thread_request,
./sds_thread/inter_thread_communication/requests/
[sds_lifecycle_request, sds_message_request, sds_dependencies_request],
../src/[reliability_utils, message],
./events/[
json_message_ready_event, json_message_sent_event, json_missing_dependencies_event,
json_periodic_sync_event,
]
################################################################################
### Wrapper around the reliability manager
################################################################################
################################################################################
### Not-exported components
template checkLibsdsParams*(
ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer
) =
ctx[].userData = userData
if isNil(callback):
return RET_MISSING_CALLBACK
template callEventCallback(ctx: ptr SdsContext, eventName: string, body: untyped) =
if isNil(ctx[].eventCallback):
error eventName & " - eventCallback is nil"
return
if isNil(ctx[].eventUserData):
error eventName & " - eventUserData is nil"
return
foreignThreadGc:
try:
let event = body
cast[SdsCallBack](ctx[].eventCallback)(
RET_OK, unsafeAddr event[0], cast[csize_t](len(event)), ctx[].eventUserData
)
except Exception, CatchableError:
let msg =
"Exception " & eventName & " when calling 'eventCallBack': " &
getCurrentExceptionMsg()
cast[SdsCallBack](ctx[].eventCallback)(
RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), ctx[].eventUserData
)
proc handleRequest(
ctx: ptr SdsContext,
requestType: RequestType,
content: pointer,
callback: SdsCallBack,
userData: pointer,
): cint =
sds_thread.sendRequestToSdsThread(ctx, requestType, content, callback, userData).isOkOr:
let msg = "libsds error: " & $error
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
return RET_OK
proc onMessageReady(ctx: ptr SdsContext): MessageReadyCallback =
return proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} =
callEventCallback(ctx, "onMessageReady"):
$JsonMessageReadyEvent.new(messageId, channelId)
proc onMessageSent(ctx: ptr SdsContext): MessageSentCallback =
return proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} =
callEventCallback(ctx, "onMessageSent"):
$JsonMessageSentEvent.new(messageId, channelId)
proc onMissingDependencies(ctx: ptr SdsContext): MissingDependenciesCallback =
return proc(messageId: SdsMessageID, missingDeps: seq[SdsMessageID], channelId: SdsChannelID) {.gcsafe.} =
callEventCallback(ctx, "onMissingDependencies"):
$JsonMissingDependenciesEvent.new(messageId, missingDeps, channelId)
proc onPeriodicSync(ctx: ptr SdsContext): PeriodicSyncCallback =
return proc() {.gcsafe.} =
callEventCallback(ctx, "onPeriodicSync"):
$JsonPeriodicSyncEvent.new()
### End of not-exported components
################################################################################
################################################################################
### Library setup
# Every Nim library must have this function called - the name is derived from
# the `--nimMainPrefix` command line option
proc libsdsNimMain() {.importc.}
# To control when the library has been initialized
var initialized: Atomic[bool]
2025-08-19 11:33:12 +05:30
proc getLogFilePath(): string =
# Check for environment variable first, fallback to default location
let envPath = getEnv("SDS_LOG_FILE")
if envPath.len > 0:
return envPath
# Default to a location that status-go can manage
let defaultDir = getEnv("SDS_LOG_DIR", "/tmp")
return joinPath(defaultDir, "nim-sds.log")
proc writeToLogFile(logLevel: LogLevel, msg: LogOutputStr) {.raises: [].} =
try:
let logFile = getLogFilePath()
let timestamp = now().format("yyyy-MM-dd HH:mm:ss.fff")
let logLine = "[$1] [$2] $3\n".format(timestamp, $logLevel, $msg)
# Create directory if it doesn't exist
let logDir = parentDir(logFile)
if not dirExists(logDir):
createDir(logDir)
# Append to log file
let file = open(logFile, fmAppend)
defer: file.close()
file.write(logLine)
except:
# Fallback to console if file writing fails
echo "[nim-sds-fallback] ", logLevel, ": ", msg
2025-08-18 18:57:13 +05:30
when defined(android):
when compiles(defaultChroniclesStream.outputs[0].writer):
defaultChroniclesStream.outputs[0].writer = proc(
logLevel: LogLevel, msg: LogOutputStr
) {.raises: [].} =
echo logLevel, msg
2025-08-18 18:57:13 +05:30
else:
when compiles(defaultChroniclesStream.outputs[0].writer):
defaultChroniclesStream.outputs[0].writer = proc(
logLevel: LogLevel, msg: LogOutputStr
) {.raises: [].} =
2025-08-19 11:33:12 +05:30
# Critical logs (ERROR, FATAL) are written to console
2025-08-18 18:57:13 +05:30
if logLevel >= LogLevel.ERROR:
echo "[nim-sds] ", logLevel, ": ", msg
2025-08-19 11:33:12 +05:30
else:
# All other logs are written to a file
writeToLogFile(logLevel, msg)
proc initializeLibrary() {.exported.} =
if not initialized.exchange(true):
## Every Nim library needs to call `<yourprefix>NimMain` once exactly, to initialize the Nim runtime.
## Being `<yourprefix>` the value given in the optional compilation flag --nimMainPrefix:yourprefix
libsdsNimMain()
when declared(setupForeignThreadGc):
setupForeignThreadGc()
when declared(nimGC_setStackBottom):
var locals {.volatile, noinit.}: pointer
locals = addr(locals)
nimGC_setStackBottom(locals)
### End of library setup
################################################################################
################################################################################
### Exported procs
proc SdsNewReliabilityManager(
callback: SdsCallBack, userData: pointer
): pointer {.dynlib, exportc, cdecl.} =
initializeLibrary()
## Creates a new instance of the Reliability Manager.
if isNil(callback):
echo "error: missing callback in NewReliabilityManager"
return nil
## Create the SDS thread that will keep waiting for req from the main thread.
var ctx = sds_thread.createSdsThread().valueOr:
let msg = "Error in createSdsThread: " & $error
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return nil
ctx.userData = userData
let appCallbacks = AppCallbacks(
messageReadyCb: onMessageReady(ctx),
messageSentCb: onMessageSent(ctx),
missingDependenciesCb: onMissingDependencies(ctx),
periodicSyncCb: onPeriodicSync(ctx),
)
let retCode = handleRequest(
ctx,
RequestType.LIFECYCLE,
SdsLifecycleRequest.createShared(
SdsLifecycleMsgType.CREATE_RELIABILITY_MANAGER, nil, appCallbacks
),
callback,
userData,
)
if retCode == RET_ERR:
return nil
return ctx
proc SdsSetEventCallback(
ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer
) {.dynlib, exportc.} =
initializeLibrary()
ctx[].eventCallback = cast[pointer](callback)
ctx[].eventUserData = userData
proc SdsCleanupReliabilityManager(
ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer
): cint {.dynlib, exportc.} =
initializeLibrary()
checkLibsdsParams(ctx, callback, userData)
sds_thread.destroySdsThread(ctx).isOkOr:
let msg = "libsds error: " & $error
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
## always need to invoke the callback although we don't retrieve value to the caller
callback(RET_OK, nil, 0, userData)
return RET_OK
proc SdsResetReliabilityManager(
ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer
): cint {.dynlib, exportc.} =
initializeLibrary()
checkLibsdsParams(ctx, callback, userData)
handleRequest(
ctx,
RequestType.LIFECYCLE,
SdsLifecycleRequest.createShared(SdsLifecycleMsgType.RESET_RELIABILITY_MANAGER),
callback,
userData,
)
proc SdsWrapOutgoingMessage(
ctx: ptr SdsContext,
message: pointer,
messageLen: csize_t,
messageId: cstring,
channelId: cstring,
callback: SdsCallBack,
userData: pointer,
): cint {.dynlib, exportc.} =
initializeLibrary()
checkLibsdsParams(ctx, callback, userData)
if message == nil and messageLen > 0:
let msg = "libsds error: " & "message pointer is NULL but length > 0"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
if messageId == nil:
let msg = "libsds error: " & "message ID pointer is NULL"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
if channelId == nil:
let msg = "libsds error: " & "channel ID pointer is NULL"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
if channelId != nil and $channelId == "":
let msg = "libsds error: " & "channel ID is empty string"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
handleRequest(
ctx,
RequestType.MESSAGE,
SdsMessageRequest.createShared(
SdsMessageMsgType.WRAP_MESSAGE, message, messageLen, messageId, channelId
),
callback,
userData,
)
proc SdsUnwrapReceivedMessage(
ctx: ptr SdsContext,
message: pointer,
messageLen: csize_t,
callback: SdsCallBack,
userData: pointer,
): cint {.dynlib, exportc.} =
initializeLibrary()
checkLibsdsParams(ctx, callback, userData)
if message == nil and messageLen > 0:
let msg = "libsds error: " & "message pointer is NULL but length > 0"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
handleRequest(
ctx,
RequestType.MESSAGE,
SdsMessageRequest.createShared(
SdsMessageMsgType.UNWRAP_MESSAGE, message, messageLen
),
callback,
userData,
)
proc SdsMarkDependenciesMet(
ctx: ptr SdsContext,
messageIds: pointer,
count: csize_t,
channelId: cstring,
callback: SdsCallBack,
userData: pointer,
): cint {.dynlib, exportc.} =
initializeLibrary()
checkLibsdsParams(ctx, callback, userData)
if messageIds == nil and count > 0:
let msg = "libsds error: " & "MessageIDs pointer is NULL but count > 0"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
if channelId == nil:
let msg = "libsds error: " & "channel ID pointer is NULL"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
if channelId != nil and $channelId == "":
let msg = "libsds error: " & "channel ID is empty string"
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData)
return RET_ERR
handleRequest(
ctx,
RequestType.DEPENDENCIES,
SdsDependenciesRequest.createShared(
SdsDependenciesMsgType.MARK_DEPENDENCIES_MET, messageIds, count, channelId
),
callback,
userData,
)
proc SdsStartPeriodicTasks(
ctx: ptr SdsContext, callback: SdsCallBack, userData: pointer
): cint {.dynlib, exportc.} =
initializeLibrary()
checkLibsdsParams(ctx, callback, userData)
handleRequest(
ctx,
RequestType.LIFECYCLE,
SdsLifecycleRequest.createShared(SdsLifecycleMsgType.START_PERIODIC_TASKS),
callback,
userData,
)
2025-08-19 11:33:12 +05:30
proc SdsSetLogFile(
logFilePath: cstring
): cint {.dynlib, exportc.} =
## Sets the log file path for nim-sds logging
## This allows applications to configure where nim-sds logs should be written
initializeLibrary()
if logFilePath == nil:
return RET_ERR
try:
putEnv("SDS_LOG_FILE", $logFilePath)
return RET_OK
except:
return RET_ERR
### End of exported procs
################################################################################