Skip to content

Commit

Permalink
Add MainActor to RemoteLogger
Browse files Browse the repository at this point in the history
  • Loading branch information
kean committed Aug 31, 2024
1 parent 4b57cae commit 8d61226
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 30 deletions.
7 changes: 7 additions & 0 deletions Sources/Pulse/LoggerStore/LoggerStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ public final class LoggerStore: @unchecked Sendable, Identifiable {
guard Thread.isMainThread else {
return DispatchQueue.main.async { register(store: store) }
}
MainActor.assumeIsolated {
_register(store: store)
}
}

@MainActor
private static func _register(store: LoggerStore) {
if RemoteLogger.shared.store == nil {
RemoteLogger.shared.initialize(store: store)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Pulse/RemoteLogger/RemoteLogger-Connection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ extension RemoteLogger {
// MARK: Helpers

extension RemoteLogger {
static func encode(code: UInt8, body: Data) throws -> Data {
nonisolated static func encode(code: UInt8, body: Data) throws -> Data {
guard body.count < UInt32.max else {
throw PacketParsingError.unsupportedContentSize
}
Expand All @@ -225,7 +225,7 @@ extension RemoteLogger {
return data
}

static func decode(buffer: Data) throws -> (Connection.Packet, Int) {
nonisolated static func decode(buffer: Data) throws -> (Connection.Packet, Int) {
let header = try PacketHeader(data: buffer)
guard buffer.count >= header.compressedPacketLength else {
throw PacketParsingError.notEnoughData
Expand Down
46 changes: 22 additions & 24 deletions Sources/Pulse/RemoteLogger/RemoteLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import OSLog

/// Connects to the remote server and sends logs remotely. In the current version,
/// a server is a Pulse Pro app for macOS).
///
/// - warning: Has to be used from the main thread.
@MainActor
public final class RemoteLogger: ObservableObject, RemoteLoggerConnectionDelegate {
/// The store that the logger was initialized with.
public private(set) var store: LoggerStore?
Expand Down Expand Up @@ -46,10 +45,14 @@ public final class RemoteLogger: ObservableObject, RemoteLoggerConnectionDelegat

@Published
public private(set) var connectionState: ConnectionState = .disconnected {
didSet { os_log("Set public connection state %{public}@", log: log, "\(oldValue)\(connectionState)") }

didSet {
os_log("Set public connection state %{public}@", log: log, "\(oldValue)\(connectionState)")
RemoteLogger.latestConnectionState.value = connectionState
}
}

nonisolated static let latestConnectionState = Mutex<ConnectionState>(.disconnected)

// Browsing
private var browser: NWBrowser?
private var selectedServerPasscode: String?
Expand Down Expand Up @@ -101,8 +104,7 @@ public final class RemoteLogger: ObservableObject, RemoteLoggerConnectionDelegat
}
}

public static var shared: RemoteLogger { _shared.value }
private static let _shared = Mutex(RemoteLogger())
public static let shared = RemoteLogger()

/// - parameter store: The store to be synced with the server. By default,
/// ``LoggerStore/shared``. Only one store can be synced at at time.
Expand All @@ -128,9 +130,8 @@ public final class RemoteLogger: ObservableObject, RemoteLoggerConnectionDelegat

// The buffer is used to cover the time between the app launch and the
// initial (automatic) connection to the server.
let box = SendableBox(value: self)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
box.value?.clearBuffer()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { [weak self] in
self?.clearBuffer()
}
}

Expand Down Expand Up @@ -201,12 +202,15 @@ public final class RemoteLogger: ObservableObject, RemoteLoggerConnectionDelegat
parameters.includePeerToPeer = true

let browser = NWBrowser(for: .bonjourWithTXTRecord(type: RemoteLogger.serviceType, domain: nil), using: parameters)
let box = SendableBox(value: self)
browser.stateUpdateHandler = {
box.value?.browserDidUpdateState($0)
browser.stateUpdateHandler = { [weak self] state in
MainActor.assumeIsolated {
self?.browserDidUpdateState(state)
}
}
browser.browseResultsChangedHandler = { results, _ in
box.value?.browserDidUpdateResults(results)
browser.browseResultsChangedHandler = { [weak self] results, _ in
MainActor.assumeIsolated {
self?.browserDidUpdateResults(results)
}
}
browser.start(queue: .main)

Expand Down Expand Up @@ -249,9 +253,8 @@ public final class RemoteLogger: ObservableObject, RemoteLoggerConnectionDelegat
os_log("Did scheduled browser retry", log: log)

// Automatically retry until the user cancels
let box = SendableBox(value: self)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
guard let self = box.value, self.isEnabled else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) { [weak self] in
guard let self, self.isEnabled else { return }
self.stopBrowser()
self.startBrowser()
}
Expand Down Expand Up @@ -440,9 +443,8 @@ public final class RemoteLogger: ObservableObject, RemoteLoggerConnectionDelegat
connection?.send(code: .clientHello, entity: body)

// Set timeout and retry in case there was no response from the server
let box = SendableBox(value: self)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10)) {
box.value?.handshakeDidTimeout()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10)) { [weak self] in
self?.handshakeDidTimeout()
}
}

Expand Down Expand Up @@ -692,10 +694,6 @@ private func getFallbackDeviceId() -> UUID {
return id
}

private struct SendableBox<T: AnyObject>: @unchecked Sendable {
weak var value: T?
}

private extension NWBrowser.Result {
var name: String? {
switch endpoint {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Pulse/RemoteLogger/RemoteLoggerURLProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public final class RemoteLoggerURLProtocol: URLProtocol {
}

public override class func canInit(with request: URLRequest) -> Bool {
guard RemoteLogger.shared.connectionState == .connected else {
guard RemoteLogger.latestConnectionState.value == .connected else {
return false
}
return RemoteDebugger.shared.shouldMock(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Combine
import Pulse
import Network

@MainActor
final class RemoteLoggerSettingsViewModel: ObservableObject {
@Published var isEnabled = false
@Published var pendingPasscodeProtectedServer: RemoteLoggerServerViewModel?
Expand All @@ -19,10 +20,10 @@ final class RemoteLoggerSettingsViewModel: ObservableObject {

static var shared = RemoteLoggerSettingsViewModel()

init(logger: RemoteLogger = .shared) {
self.logger = logger
init(logger: RemoteLogger? = nil) {
self.logger = logger ?? .shared

isEnabled = logger.isEnabled
isEnabled = self.logger.isEnabled

$isEnabled.dropFirst().removeDuplicates().receive(on: DispatchQueue.main)
.throttle(for: .milliseconds(500), scheduler: DispatchQueue.main, latest: true)
Expand Down
1 change: 1 addition & 0 deletions Sources/PulseUI/Views/ContextMenus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ struct ButtonOpenOnMac: View {
}
}

@MainActor
private func openOnMac(_ entity: NSManagedObject) {
switch LoggerEntity(entity) {
case .message(let message):
Expand Down

0 comments on commit 8d61226

Please sign in to comment.