murky problem again
This commit is contained in:
+1
-1
@@ -74,7 +74,7 @@ API calls go through the generated `Client` over `URLSessionTransport`. A middle
|
|||||||
|
|
||||||
`EventStream` is a hand-written SSE client over `URLSession.bytes(for:)`. It splits lines itself, because `AsyncBytes.lines` drops the blank lines that end SSE events, and parses off the main actor, handing each frame to it. It handles `id`, `event: ping`, `event: ready` and `data`: it tracks the last seq, reconnects from it after a stale threshold (40 s, checked every 5 s) and a retry delay (2 s), when the network comes back (`NWPathMonitor`), and when the app becomes active with a stale stream. An event whose data doesn't decode is logged and skipped. There are two streams, global (after the bootstrap `last_event_seq`) and lead (`?lead=true`, after the last event of the lead's first page), and only the global one drives the connection dot.
|
`EventStream` is a hand-written SSE client over `URLSession.bytes(for:)`. It splits lines itself, because `AsyncBytes.lines` drops the blank lines that end SSE events, and parses off the main actor, handing each frame to it. It handles `id`, `event: ping`, `event: ready` and `data`: it tracks the last seq, reconnects from it after a stale threshold (40 s, checked every 5 s) and a retry delay (2 s), when the network comes back (`NWPathMonitor`), and when the app becomes active with a stale stream. An event whose data doesn't decode is logged and skipped. There are two streams, global (after the bootstrap `last_event_seq`) and lead (`?lead=true`, after the last event of the lead's first page), and only the global one drives the connection dot.
|
||||||
|
|
||||||
Startup: `GET /api/admin/status`, then me, projects, inbox, lead sessions and summary reads in parallel, then drafts, focus, the lead stream, the window, the global stream. A failure before the window shows replaces it with `byre failed to start: <error>`; connecting to the same URL again from New Server Window restarts it, and Forget Server leaves that screen. `handleEvent` calls `notifyInbox` on `inbox.created` and `notifyTurn` on `turn.ended`.
|
Startup: `GET /api/admin/status`, then me, projects, inbox, lead sessions and summary reads in parallel, then drafts, focus, the lead stream, the window, the global stream. A failure before the window shows replaces it with `byre failed to start: <error>` and the whole startup is tried again every 2 s until it succeeds, so a server restarting (the app's own kickstart after an update, for one) heals by itself; Forget Server leaves that screen. `handleEvent` calls `notifyInbox` on `inbox.created` and `notifyTurn` on `turn.ended`.
|
||||||
|
|
||||||
The app logs through `os.Logger` with subsystem `run.fc.byre.mac`: `log show --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` prints it.
|
The app logs through `os.Logger` with subsystem `run.fc.byre.mac`: `log show --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` prints it.
|
||||||
|
|
||||||
|
|||||||
@@ -61,9 +61,6 @@ final class Servers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func add(_ origin: URL) {
|
func add(_ origin: URL) {
|
||||||
if let connection = connections[origin.absoluteString], case .failed = connection.phase {
|
|
||||||
connections[origin.absoluteString] = nil
|
|
||||||
}
|
|
||||||
if record(origin.absoluteString) == nil {
|
if record(origin.absoluteString) == nil {
|
||||||
let used = Set(entries.map(\.record.color))
|
let used = Set(entries.map(\.record.color))
|
||||||
let color = taskPalette.indices.first { !used.contains($0) } ?? entries.count % taskPalette.count
|
let color = taskPalette.indices.first { !used.contains($0) } ?? entries.count % taskPalette.count
|
||||||
@@ -140,6 +137,12 @@ final class Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func start() async {
|
private func start() async {
|
||||||
|
while !stopped, !(await attempt()) {
|
||||||
|
try? await Task.sleep(for: .seconds(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func attempt() async -> Bool {
|
||||||
let store = Store(api: API(origin: origin))
|
let store = Store(api: API(origin: origin))
|
||||||
do {
|
do {
|
||||||
let status = try await store.api.client.getStatus().ok.body.json
|
let status = try await store.api.client.getStatus().ok.body.json
|
||||||
@@ -153,7 +156,7 @@ final class Connection {
|
|||||||
try await store.loadFocus()
|
try await store.loadFocus()
|
||||||
guard !stopped else {
|
guard !stopped else {
|
||||||
await store.stop()
|
await store.stop()
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
Task {
|
Task {
|
||||||
await store.startLeadStream()
|
await store.startLeadStream()
|
||||||
@@ -162,11 +165,13 @@ final class Connection {
|
|||||||
store.startGlobalStream(after: status.lastEventSeq)
|
store.startGlobalStream(after: status.lastEventSeq)
|
||||||
store.remote.start()
|
store.remote.start()
|
||||||
Notifier.shared.deliver()
|
Notifier.shared.deliver()
|
||||||
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
await store.stop()
|
await store.stop()
|
||||||
if !stopped {
|
if !stopped {
|
||||||
phase = .failed("byre failed to start: \(errorText(error))")
|
phase = .failed("byre failed to start: \(errorText(error))")
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user