Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions flutter/lib/logseq_chat_native_bridge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:ffi';
import 'dart:isolate';

import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';

import 'lui_dispatch.dart';
import 'native_effect_drain.dart';
Expand Down Expand Up @@ -228,6 +229,7 @@ final class LogseqChatNativeBridge
required String message,
}) {
if (_runtimeScheduler.isCoreBusy) {
debugPrint('[NativeBridge] resolveEffect id=$id queued (core busy)');
_runtimeScheduler.runUi(
() => _apply(
_resolveEffectWithMessage(
Expand All @@ -239,9 +241,14 @@ final class LogseqChatNativeBridge
);
return '';
}
return _read(
debugPrint('[NativeBridge] resolveEffect id=$id call');
final result = _read(
_resolveEffectWithMessage(id: id, succeeded: succeeded, message: message),
);
debugPrint(
'[NativeBridge] resolveEffect id=$id returned chars=${result.length}',
);
return result;
}

Pointer<Utf8> _resolveEffectWithMessage({
Expand All @@ -260,12 +267,21 @@ final class LogseqChatNativeBridge
@override
String applySnapshot(String response) {
if (_runtimeScheduler.isCoreBusy) {
debugPrint(
'[NativeBridge] applySnapshot queued (core busy) '
'chars=${response.length}',
);
_runtimeScheduler.runUi(
() => _apply(_applySnapshotWithResponse(response)),
);
return '';
}
return _read(_applySnapshotWithResponse(response));
debugPrint('[NativeBridge] applySnapshot call chars=${response.length}');
final result = _read(_applySnapshotWithResponse(response));
debugPrint(
'[NativeBridge] applySnapshot returned chars=${result.length}',
);
return result;
}

Pointer<Utf8> _applySnapshotWithResponse(String response) {
Expand All @@ -280,12 +296,19 @@ final class LogseqChatNativeBridge
@override
String applyHostUpdate({required String kind, required String payload}) {
if (_runtimeScheduler.isCoreBusy) {
debugPrint('[NativeBridge] applyHostUpdate kind=$kind queued (core busy)');
_runtimeScheduler.runUi(
() => _apply(_applyHostUpdateWithPayload(kind, payload)),
);
return '';
}
return _read(_applyHostUpdateWithPayload(kind, payload));
debugPrint('[NativeBridge] applyHostUpdate kind=$kind call');
final result = _read(_applyHostUpdateWithPayload(kind, payload));
debugPrint(
'[NativeBridge] applyHostUpdate kind=$kind returned '
'chars=${result.length}',
);
return result;
}

Pointer<Utf8> _applyHostUpdateWithPayload(String kind, String payload) {
Expand Down
2 changes: 2 additions & 0 deletions flutter/lib/native_effect_drain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ final class NativeEffectDrain {

void _apply(String patch, String source) {
if (patch.isNotEmpty) {
trace('applying patch from $source chars=${patch.length}');
applyPatch(patch);
trace('applied patch from $source');
} else {
// An OCaml exception in a lui_* FFI call surfaces as an empty patch —
// log the producer so a wedged pipeline is visible in the drain trace.
Expand Down
38 changes: 24 additions & 14 deletions shared/native/logseq_chat_core_ffi.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
#include <stdlib.h>
#include <string.h>

#ifdef __ANDROID__
#include <android/log.h>
#define LOGSEQ_CHAT_LOG(...) \
__android_log_print(ANDROID_LOG_ERROR, "logseq_chat", __VA_ARGS__)
#else
#define LOGSEQ_CHAT_LOG(...) \
do { \
fprintf(stderr, "logseq_chat: "); \
fprintf(stderr, __VA_ARGS__); \
fputc('\n', stderr); \
} while (0)
#endif

static pthread_once_t logseq_chat_runtime_once = PTHREAD_ONCE_INIT;
static pthread_t logseq_chat_runtime_thread;
static _Thread_local char *logseq_chat_response = NULL;
Expand Down Expand Up @@ -122,29 +135,27 @@ static const char *missing_lui_callback(void) {
cannot ride back in-band — the host would try to parse it as a patch.
An empty patch is silently skipped by the drain, which is exactly the
wedge signature we hit when an exception wedged the pipeline: log the
exception to stderr (logcat on Android, console on iOS) so the failure
is diagnosable instead of invisible. */
exception via the platform logger (stderr does not reach logcat on
Android) so the failure is diagnosable instead of invisible. */
static const char *lui_no_callback(const char *name) {
fprintf(stderr, "logseq_chat: OCaml LUI callback is not registered: %s\n",
name);
LOGSEQ_CHAT_LOG("OCaml LUI callback is not registered: %s", name);
return missing_lui_callback();
}

static const char *lui_bad_argument(const char *name) {
fprintf(stderr, "logseq_chat: NULL argument passed to %s\n", name);
LOGSEQ_CHAT_LOG("NULL argument passed to %s", name);
return missing_lui_callback();
}

static const char *lui_exception(const char *name, value result) {
char *message = caml_format_exception(Extract_exception(result));
fprintf(stderr, "logseq_chat: OCaml exception in %s: %s\n",
name, message == NULL ? "(unprintable)" : message);
LOGSEQ_CHAT_LOG("OCaml exception in %s: %s",
name, message == NULL ? "(unprintable)" : message);
return missing_lui_callback();
}

static const char *lui_thread_registration_failed(void) {
fprintf(stderr, "logseq_chat: could not register calling thread "
"with the OCaml runtime\n");
LOGSEQ_CHAT_LOG("could not register calling thread with the OCaml runtime");
return missing_lui_callback();
}

Expand Down Expand Up @@ -417,15 +428,14 @@ int64_t logseq_chat_lui_root_node(void) {
if (registration < 0) return node;
const value *callback = caml_named_value("logseq_chat_lui_root_node");
if (callback == NULL) {
fprintf(stderr, "logseq_chat: OCaml LUI callback is not registered: "
"logseq_chat_lui_root_node\n");
LOGSEQ_CHAT_LOG("OCaml LUI callback is not registered: "
"logseq_chat_lui_root_node");
} else {
value result = caml_callback_exn(*callback, Val_unit);
if (Is_exception_result(result)) {
char *message = caml_format_exception(Extract_exception(result));
fprintf(stderr, "logseq_chat: OCaml exception in "
"logseq_chat_lui_root_node: %s\n",
message == NULL ? "(unprintable)" : message);
LOGSEQ_CHAT_LOG("OCaml exception in logseq_chat_lui_root_node: %s",
message == NULL ? "(unprintable)" : message);
} else {
node = Long_val(result);
}
Expand Down
Loading