mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-12-21 11:10:08 +01:00
♻️ Make ValetServicesManager more crash-resistant
This commit is contained in:
@@ -3920,7 +3920,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1681;
|
CURRENT_PROJECT_VERSION = 1682;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG = YES;
|
DEBUG = YES;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
@@ -3964,7 +3964,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1681;
|
CURRENT_PROJECT_VERSION = 1682;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG = NO;
|
DEBUG = NO;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
@@ -4146,7 +4146,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1681;
|
CURRENT_PROJECT_VERSION = 1682;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG = YES;
|
DEBUG = YES;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
@@ -4339,7 +4339,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1681;
|
CURRENT_PROJECT_VERSION = 1682;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG = NO;
|
DEBUG = NO;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func grepContains(
|
|||||||
Attempts to introduce sleep for a particular duration. Use with caution.
|
Attempts to introduce sleep for a particular duration. Use with caution.
|
||||||
*/
|
*/
|
||||||
func delay(seconds: Double) async {
|
func delay(seconds: Double) async {
|
||||||
try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
try? await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,49 +34,45 @@ class ValetServicesManager: ServicesManager {
|
|||||||
This method allows us to reload the Homebrew services, but we run this command
|
This method allows us to reload the Homebrew services, but we run this command
|
||||||
twice (once for user services, and once for root services). Please note that
|
twice (once for user services, and once for root services). Please note that
|
||||||
these two commands are executed concurrently.
|
these two commands are executed concurrently.
|
||||||
|
|
||||||
|
If this fails, question marks will be displayed in the menu bar and we will
|
||||||
|
try one more time to reload the services.
|
||||||
*/
|
*/
|
||||||
override func reloadServicesStatus() async {
|
override func reloadServicesStatus() async {
|
||||||
|
await reloadServicesStatus(isRetry: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reloadServicesStatus(isRetry: Bool) async {
|
||||||
if !Valet.installed {
|
if !Valet.installed {
|
||||||
return Log.info("Not reloading services because running in Standalone Mode.")
|
return Log.info("Not reloading services because running in Standalone Mode.")
|
||||||
}
|
}
|
||||||
|
|
||||||
await withTaskGroup(of: [HomebrewService].self, body: { group in
|
await withTaskGroup(of: [HomebrewService].self, body: { group in
|
||||||
// First, retrieve the status of the formulae that run as root
|
// Retrieve the status of the formulae that run as root
|
||||||
group.addTask {
|
group.addTask {
|
||||||
let rootServiceNames = self.formulae
|
await self.fetchHomebrewServices(elevated: true)
|
||||||
.filter { $0.elevated }
|
|
||||||
.map { $0.name }
|
|
||||||
|
|
||||||
let rootJson = await self.container.shell
|
|
||||||
.pipe("sudo \(self.container.paths.brew) services info --all --json")
|
|
||||||
.out.data(using: .utf8)!
|
|
||||||
|
|
||||||
return try! JSONDecoder()
|
|
||||||
.decode([HomebrewService].self, from: rootJson)
|
|
||||||
.filter({ return rootServiceNames.contains($0.name) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// At the same time, retrieve the status of the formulae that run as user
|
// At the same time, retrieve the status of the formulae that run as user
|
||||||
group.addTask {
|
group.addTask {
|
||||||
let userServiceNames = self.formulae
|
await self.fetchHomebrewServices(elevated: false)
|
||||||
.filter { !$0.elevated }
|
|
||||||
.map { $0.name }
|
|
||||||
|
|
||||||
let normalJson = await self.container.shell
|
|
||||||
.pipe("\(self.container.paths.brew) services info --all --json")
|
|
||||||
.out.data(using: .utf8)!
|
|
||||||
|
|
||||||
return try! JSONDecoder()
|
|
||||||
.decode([HomebrewService].self, from: normalJson)
|
|
||||||
.filter({ return userServiceNames.contains($0.name) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that Homebrew services' output is stored
|
// Ensure that Homebrew services' output is stored
|
||||||
self.homebrewServices = []
|
self.homebrewServices = []
|
||||||
|
|
||||||
for await services in group {
|
for await services in group {
|
||||||
homebrewServices.append(contentsOf: services)
|
homebrewServices.append(contentsOf: services)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we didn't get any service data and this isn't a retry, try again
|
||||||
|
if self.homebrewServices.isEmpty && !isRetry {
|
||||||
|
Log.warn("Failed to retrieve any Homebrew services data. Retrying once in 2 seconds...")
|
||||||
|
await delay(seconds: 2)
|
||||||
|
await self.reloadServicesStatus(isRetry: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch the update of the new service wrappers
|
// Dispatch the update of the new service wrappers
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
// Ensure both commands complete (but run concurrently)
|
// Ensure both commands complete (but run concurrently)
|
||||||
@@ -95,6 +91,43 @@ class ValetServicesManager: ServicesManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Fetches Homebrew services information for either elevated (root) or user services.
|
||||||
|
|
||||||
|
- Parameter elevated: Whether to fetch services running as root (true) or user (false)
|
||||||
|
- Returns: Array of HomebrewService objects, or empty array if fetching fails
|
||||||
|
*/
|
||||||
|
private func fetchHomebrewServices(elevated: Bool) async -> [HomebrewService] {
|
||||||
|
// Check which formulae we are supposed to be looking for
|
||||||
|
let serviceNames = self.formulae
|
||||||
|
.filter { $0.elevated == elevated }
|
||||||
|
.map { $0.name }
|
||||||
|
|
||||||
|
// Determine which command to run
|
||||||
|
let command = elevated
|
||||||
|
? "sudo \(self.container.paths.brew) services info --all --json"
|
||||||
|
: "\(self.container.paths.brew) services info --all --json"
|
||||||
|
|
||||||
|
// Run and get the output of the command
|
||||||
|
let output = await self.container.shell.pipe(command).out
|
||||||
|
|
||||||
|
// Attempt to parse the output
|
||||||
|
guard let jsonData = output.data(using: .utf8) else {
|
||||||
|
Log.err("Failed to convert \(elevated ? "root" : "user") services output to UTF-8 data. Output: \(output)")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to decode the JSON output. In certain situations the output may not be valid and this prevents a crash
|
||||||
|
do {
|
||||||
|
return try JSONDecoder()
|
||||||
|
.decode([HomebrewService].self, from: jsonData)
|
||||||
|
.filter { serviceNames.contains($0.name) }
|
||||||
|
} catch {
|
||||||
|
Log.err("Failed to decode \(elevated ? "root" : "user") services JSON: \(error). Output: \(output)")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func toggleService(named: String) async {
|
override func toggleService(named: String) async {
|
||||||
guard let wrapper = self[named] else {
|
guard let wrapper = self[named] else {
|
||||||
return Log.err("The wrapper for '\(named)' is missing.")
|
return Log.err("The wrapper for '\(named)' is missing.")
|
||||||
|
|||||||
Reference in New Issue
Block a user