git.iko.soy/parseable-macos
on master help sign in
Fix 4 bugs: poll overlap on restart, stale sort task, schema stream mismatch, stale error message
c21859aiko
ParseableViewer/ViewModels/LiveTailViewModel.swift+10−2
@@ -420,10 +420,18 @@ final class LiveTailViewModel {
420 }
420 }
421
421
422 private func poll(client: ParseableClient, stream: String) async {
422 private func poll(client: ParseableClient, stream: String) async {
423+ let generation = pollGeneration
423 guard !isPolling else { return }
424 guard !isPolling else { return }
424 isPolling = true
425 isPolling = true
425− defer { isPolling = false }
426+ // Only release the guard if this poll is still the current generation. A
426− let generation = pollGeneration
427+ // stop()/start() during the network await can supersede this poll while a
428+ // newer-generation poll is already in flight; clearing isPolling
429+ // unconditionally would let a subsequent timer tick start a second poll
430+ // that overlaps the newer one, racing writes to lastTimestamp /
431+ // consecutiveErrors. start()/clear() reset isPolling on their own
432+ // generation bump, so a superseded poll leaving it set can't wedge the
433+ // new timer.
434+ defer { if generation == pollGeneration { isPolling = false } }
427
435
428 let now = Date()
436 let now = Date()
429 // The Parseable server truncates query time-ranges to minute
437 // The Parseable server truncates query time-ranges to minute
ParseableViewer/ViewModels/QueryViewModel.swift+1−1
@@ -79,7 +79,7 @@ final class QueryViewModel {
79 var schemaFields: [SchemaField] = []
79 var schemaFields: [SchemaField] = []
80 /// The stream of the most recent `loadSchema` call, used to drop a stale
80 /// The stream of the most recent `loadSchema` call, used to drop a stale
81 /// in-flight response when the user rapidly switches streams.
81 /// in-flight response when the user rapidly switches streams.
82− private var latestSchemaStream: String?
82+ private(set) var latestSchemaStream: String?
83
83
84 // Query task for cancellation
84 // Query task for cancellation
85 private var queryTask: Task<Void, Never>?
85 private var queryTask: Task<Void, Never>?
ParseableViewer/Views/ContentView.swift+4−1
@@ -19,7 +19,10 @@ struct ContentView: View {
19 ConnectionSheet(connection: appState.editingConnection)
19 ConnectionSheet(connection: appState.editingConnection)
20 }
20 }
21 .alert("Error", isPresented: $appState.showError) {
21 .alert("Error", isPresented: $appState.showError) {
22+ Button("OK") {
22− Button("OK") { appState.showError = false }
23+ appState.showError = false
24+ appState.errorMessage = nil
25+ }
23 } message: {
26 } message: {
24 Text(appState.errorMessage ?? "An unknown error occurred")
27 Text(appState.errorMessage ?? "An unknown error occurred")
25 }
28 }
ParseableViewer/Views/LiveTailView.swift+1
@@ -272,6 +272,7 @@ struct LiveTailView: View {
272 }
272 }
273 .onDisappear {
273 .onDisappear {
274 viewModel.stop()
274 viewModel.stop()
275+ liveTailSortTask?.cancel()
275 }
276 }
276 .onChange(of: appState.selectedStream) { _, _ in
277 .onChange(of: appState.selectedStream) { _, _ in
277 if viewModel.isRunning {
278 if viewModel.isRunning {
ParseableViewer/Views/QueryView.swift+6−1
@@ -303,7 +303,12 @@ struct QueryView: View {
303 }
303 }
304 .onAppear {
304 .onAppear {
305 if let stream = appState.selectedStream {
305 if let stream = appState.selectedStream {
306+ // Reload the schema when it's missing OR when the loaded schema
307+ // belongs to a different stream than the current one — otherwise a
308+ // viewModel that survives with another stream's schema would keep
309+ // autocomplete pointed at the wrong stream's fields until the next
310+ // stream switch.
306− if viewModel.schemaFields.isEmpty {
311+ if viewModel.schemaFields.isEmpty || viewModel.latestSchemaStream != stream {
307 Task {
312 Task {
308 await viewModel.loadSchema(client: appState.client, stream: stream)
313 await viewModel.loadSchema(client: appState.client, stream: stream)
309 }
314 }