Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import LUIAppleBackend
}
}
let journalAppIcons: [String: String] = [:]
@MainActor enum JournalLUIEvents {
enum JournalLUIEvents {
static func dispatch(_ event: LUIEvent) -> Int32 { 1 }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ public struct LUIAppleExtensionRegistry { public init() {} }
@MainActor public final class LUIAppleBackend {
public var rootIDs: [Int] = []
public var onEvent: ((LUIEvent) -> Void)?
public var applied: [String] = []
public var onApply: ((String) -> Void)?
public init(appIcons: [String: String], extensionRegistry: LUIAppleExtensionRegistry) throws {}
public func apply(json: String) throws {
if json == "invalid" { throw NSError(domain: "fixture", code: 1) }
rootIDs = [42]
applied.append(json)
onApply?(json)
}
}
53 changes: 45 additions & 8 deletions apple-tests/platform-services/JournalRuntimeStartupBridge.c
Original file line number Diff line number Diff line change
@@ -1,23 +1,60 @@
#include <stdint.h>
#include <pthread.h>
#include <string.h>
int32_t journal_ocaml_failure_reason(int32_t operation){return 2;}
#include <assert.h>
static void (*request)(const char*,int32_t); static int mode,inside,calls,reentrant;
static void (*patches)(const char*), (*wake)(void);
static pthread_t owner;
static int owner_set, pump_count, event_count, pumped, stop_count;
static pthread_mutex_t pump_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t pump_ready = PTHREAD_COND_INITIALIZER;
static void check_owner(void) {
if(mode<6)return;
assert(!pthread_main_np());
if(!owner_set){owner=pthread_self();owner_set=1;}
assert(pthread_equal(owner,pthread_self()));
}
int32_t journal_ocaml_failure_reason(int32_t operation){check_owner();return 2;}
void fixture_pump_while_ui_busy(void){
pthread_mutex_lock(&pump_lock);
int previous=pumped;
wake();
while(pumped==previous)pthread_cond_wait(&pump_ready,&pump_lock);
pthread_mutex_unlock(&pump_lock);
}
int32_t fixture_inside(void){return inside;}
int32_t fixture_calls(void){return calls;}
int32_t fixture_reentrant(void){return reentrant;}
void fixture_mode(int32_t m){mode=m;}
void fixture_emit(void){char bytes[32]={76,74,80,50,2,0,6,0};if(mode==5){request(bytes,32);memset(bytes,9,32);}else{char small[]={1,2};request(small,2);memset(small,9,2);}}
void* worker(void*x){fixture_emit();return 0;}
int32_t lui_ocaml_start(void (*patch)(const char*),int32_t p,int32_t h,const char*d,int32_t n){
check_owner();patches=patch;
inside=1;
if(mode==3){pthread_t t;pthread_create(&t,0,worker,0);pthread_join(t,0);}else{fixture_emit();if(mode==4)fixture_emit();}
patch(mode==2?"invalid":"valid");inside=0;return mode==1?0:1;
patch(mode==2?"invalid":mode==6?"1":"valid");inside=0;return mode==1?0:1;
}
int32_t lui_ocaml_stop(void){
check_owner();
if(mode==7 && ++stop_count<3){patches("retired");return 0;}
return 1;
}
int32_t lui_ocaml_stop(void){return 1;}
void journal_ocaml_set_wakeup_callback(void(*f)(void)){}
void journal_ocaml_set_wakeup_callback(void(*f)(void)){wake=f;}
void journal_ocaml_set_platform_request_callback(void(*f)(const char*,int32_t)){request=f;}
int32_t journal_ocaml_pump(void){return 1;}
int32_t journal_ocaml_platform_event(const char*d,int32_t n){if(inside)reentrant++;return 1;}
int32_t journal_ocaml_platform_response(const char*d,int32_t n){calls++;if(inside)reentrant++;return 1;}
int32_t journal_ocaml_platform_failure(const char*d,int32_t n){calls++;if(inside)reentrant++;return 1;}
int32_t journal_ocaml_pump(void){
check_owner();
if(mode==6){
if(++pump_count==1){
patches("2");patches("3");
}else{patches(pump_count==2?"6":"7");}
pthread_mutex_lock(&pump_lock);pumped=pump_count;pthread_cond_signal(&pump_ready);pthread_mutex_unlock(&pump_lock);
}
return 1;
}
int32_t journal_ocaml_platform_event(const char*d,int32_t n){
check_owner();if(inside)reentrant++;
if(mode==6){patches(event_count++==0?"4":"5");if(event_count==1)wake();}
return 1;
}
int32_t journal_ocaml_platform_response(const char*d,int32_t n){check_owner();calls++;if(inside)reentrant++;return 1;}
int32_t journal_ocaml_platform_failure(const char*d,int32_t n){check_owner();calls++;if(inside)reentrant++;return 1;}
49 changes: 46 additions & 3 deletions apple-tests/platform-services/JournalRuntimeStartupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import LUIAppleBackend
@_silgen_name("fixture_reentrant") func reentrant() -> Int32
@_silgen_name("fixture_mode") func mode(_ m: Int32)
@_silgen_name("fixture_emit") func emit()
@_silgen_name("fixture_pump_while_ui_busy") func pumpWhileUIBusy()
let journalAppIcons: [String: String] = [:]
@MainActor enum JournalLUIEvents { static func dispatch(_ e: LUIEvent) -> Int32 { 1 } }
enum JournalLUIEvents { static func dispatch(_ e: LUIEvent) -> Int32 { 1 } }
@MainActor final class JournalApplicationPlatform {
var connected = false, hold = true
var begins = 0
Expand All @@ -25,7 +26,7 @@ let journalAppIcons: [String: String] = [:]
@MainActor @main struct Test {
static func main() async throws {
let scenario = CommandLine.arguments[1]
mode(["rejected": 1, "patch-failure": 2, "worker": 3, "multiple": 4,"same-tag":5][scenario] ?? 0)
mode(["rejected": 1, "patch-failure": 2, "worker": 3, "multiple": 4,"same-tag":5,"core-order":6,"core-lease":7][scenario] ?? 0)
let p = JournalApplicationPlatform()
let r = try JournalRuntime(
platform: p, startupPayload: Data(), extensionRegistry: LUIAppleExtensionRegistry())
Expand All @@ -35,7 +36,49 @@ let journalAppIcons: [String: String] = [:]
try await r.waitForInitialPlatformRequests()
published = true
}
if scenario == "rejected" || scenario == "patch-failure" {
if scenario == "core-lease" {
while p.waiters.isEmpty { await Task.yield() }
p.waiters.removeFirst().resume(returning: Data([1, 2]))
try await boot.value
let replacement = try JournalRuntime(
platform: p, startupPayload: Data(), extensionRegistry: LUIAppleExtensionRegistry())
do { try replacement.start(); preconditionFailure("replaced a live lease") }
catch {}
precondition(p.connected)
precondition(!r.stop(), "fixture stop must fail")
do { try replacement.start(); preconditionFailure("started before failed cleanup settled") }
catch {}
precondition(!p.connected)
p.hold = false
try replacement.start()
try await replacement.waitForInitialPlatformRequests()
precondition(r.stop(), "late stop did not retire the old facade")
for _ in 0..<20 { await Task.yield() }
precondition(p.connected && replacement.failure == nil && r.backend.applied == ["valid"],
"retired facade disconnected its replacement or consumed an old patch")
precondition(replacement.stop())
} else if scenario == "core-order" {
while p.waiters.isEmpty { await Task.yield() }
p.waiters.removeFirst().resume(returning: Data([1, 2]))
try await boot.value
pumpWhileUIBusy()
var deferred: CheckedContinuation<Void, Never>?
r.backend.onApply = { patch in
if patch == "2" { r.sendPlatformEvent(Data([1])) }
if patch == "6" { deferred?.resume(); deferred = nil }
}
r.sendPlatformEvent(Data([1]))
precondition(r.backend.applied == ["1", "2", "3", "4", "5"],
"asynchronous pump, synchronous transaction and nested patch order changed")
await withCheckedContinuation { deferred = $0 }
precondition(r.backend.applied == ["1", "2", "3", "4", "5", "6"],
"the deferred wake was not delivered after the synchronous transaction")
pumpWhileUIBusy()
r.stop()
for _ in 0..<20 { await Task.yield() }
precondition(r.backend.applied == ["1", "2", "3", "4", "5", "6"],
"a retired generation applied its queued patch")
} else if scenario == "rejected" || scenario == "patch-failure" {
do {
try await boot.value
preconditionFailure("rejected startup completed")
Expand Down
4 changes: 2 additions & 2 deletions swift/JournalLUIEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import LUIAppleBackend

// Match LUI's typed native ABI. Journal's C adapter also acquires/releases the
// OCaml runtime lock and delivers an owned patch after releasing it, before
// returning. A backend patch can therefore synchronously dispatch another event.
// returning. The host serializes these calls on its fixed application-core thread.
@_silgen_name("lui_ocaml_appear")
private func luiOCamlAppear(_ node: Int64) -> Int32
@_silgen_name("lui_ocaml_press")
Expand Down Expand Up @@ -66,7 +66,7 @@ private func journalOCamlExtensionEvent(
_ node: Int64, _ name: UnsafePointer<CChar>?, _ payload: UnsafePointer<CChar>?
) -> Int32

@MainActor enum JournalLUIEvents {
enum JournalLUIEvents {
static func dispatch(_ event: LUIEvent) -> Int32 {
switch event {
case .appear(let node): return luiOCamlAppear(Int64(node))
Expand Down
Loading
Loading