Skip to content

Commit

Permalink
refactor: use semaphore for queues
Browse files Browse the repository at this point in the history
  • Loading branch information
mishamyrt committed Jul 14, 2024
1 parent 757c7ec commit dc6bf8e
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 71 deletions.
9 changes: 2 additions & 7 deletions Sources/Events/EventObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,16 @@ import Cocoa
class EventObserver: EventListener, EventProvider {
var listener: EventListener?

var sources: [EventSource] = []
let sources: [EventSource]

init(_ sources: [EventSource]) {
self.sources = sources
for var source in sources {
source.listener = self
}
self.sources = sources
}

func handle(_ event: Event) {
var message = "\(event.source.cyan) emitted \(event.kind.blue) event"
if !event.target.isEmpty {
message += " with \(event.target.yellow)"
}
logger.debug(message)
guard let listener else {
return
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/RunOn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import ArgumentParser
import ServiceManagement

let kAppId = "co.myrt.runon"
var logger = Logger(level: .error)
var logger = Logger(config: .init(
level: .error,
showTimestamp: true
))

@main
struct RunOn: ParsableCommand {
Expand Down
42 changes: 42 additions & 0 deletions Sources/Runner/ActionGroupQueue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation
import Shellac

enum ActionError: Error {
/// Queue is currently busy
case busy
}

/// ActionGroupQueue represents a queue of actions.
/// Helps ensure that groups of commands are executed correctly
/// and allows for fine tuning of debounce.
class ActionGroupQueue {
let name: String
let interval: TimeInterval

private let semaphore = DispatchSemaphore(value: 1)

init(name: String, interval: TimeInterval? = nil) {
self.name = name
guard let interval = interval else {
self.interval = 0
return
}
self.interval = interval
}

/// Run action and return output
func run(_ action: Action) throws -> String {
if semaphore.wait(timeout: .now()) == .timedOut {
throw ActionError.busy
}
let output = try shell(with: action.commands, timeout: action.timeout)
if interval == 0 {
semaphore.signal()
return output
}
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
self.semaphore.signal()
}
return output
}
}
55 changes: 55 additions & 0 deletions Sources/Runner/ActionLogger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Shellac

struct ActionRunnerLogger {
private let logger: Logger

init(with logger: Logger) {
self.logger = logger
}

func actionNotFound() {
self.logger.info("action not found, skipping")
}

func queueNotFound(_ name: String) {
self.logger.error("queue not found: \(name)")
}

func actionSuccess(_ output: String) {
self.logger.info("command successfully finished".green)
if !output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
self.logger.debug("output: \(output)")
}
}

func actionStarted(_ action: Action) {
var message = "starting action \(action.source.cyan):\(action.kind.blue)"
if let target = action.target {
message += " with \(target.yellow)"
}
message += " on \(action.group.magenta)"
self.logger.info(message)
}

func actionFailed(_ error: Error) {
if let error = error as? ShellError {
self.logger.error("the process exited with a non-zero status code: \(error.code).")
if !error.error.isEmpty {
self.logger.error("output: \(error.error)")
}
} else if let error = error as? ActionError {
switch error {
case .busy:
self.logger.info("queue busy, skipping")
}
} else {
self.logger.error(String(describing: error))
}
}

func eventReceived(_ event: Event) {
self.logger.info("received event \(event.source.cyan):\(event.kind.blue) with \(event.target.yellow)")
}
}

let kActionLogger = ActionRunnerLogger(with: logger.child(prefix: "Runner"))
59 changes: 0 additions & 59 deletions Sources/Runner/ActionQueue.swift

This file was deleted.

21 changes: 17 additions & 4 deletions Sources/Runner/ActionRunner.swift
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import Foundation
import Shellac

typealias ActionGroupQueueMap = [String: ActionGroupQueue]

class ActionRunner: EventListener {
let handler: ConfigHandler
var queues: [String: ActionQueue] = [:]
var queues: ActionGroupQueueMap = [:]

init(with handler: ConfigHandler) {
self.handler = handler
for (name, group) in handler.groupMap {
queues[name] = ActionQueue(
queues[name] = ActionGroupQueue(
name: name,
interval: group.debounce
)
}
}

func handle(_ event: Event) {
kActionLogger.eventReceived(event)
guard let action = handler.findAction(
source: event.source,
kind: event.kind,
target: event.target
) else {
logger.debug("handler not found")
kActionLogger.actionNotFound()
return
}
queues[action.group]?.run(action)
guard let queue = queues[action.group] else {
kActionLogger.queueNotFound(action.group)
return
}
do {
kActionLogger.actionStarted(action)
let output = try queue.run(action)
kActionLogger.actionSuccess(output)
} catch {
kActionLogger.actionFailed(error)
}
}
}

0 comments on commit dc6bf8e

Please sign in to comment.