Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] ProcessID, Signal, SignalSet, TaskInfo, ResourceUsageInfo #20

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
16 changes: 16 additions & 0 deletions Sources/CSystem/include/CSystemDarwin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2020 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

#ifdef __MACH__
#include "libproc.h"
#else
#error "whoops"
#endif

//
4 changes: 4 additions & 0 deletions Sources/CSystem/shims.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@
#if defined(_WIN32)
#include <CSystemWindows.h>
#endif

#ifdef __MACH__
#include <CSystemDarwin.h>
#endif
1 change: 1 addition & 0 deletions Sources/System/Platform/Platform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

@_implementationOnly import SystemInternals


// Public typealiases that can't be reexported from SystemInternals

/// The C `mode_t` type.
Expand Down
30 changes: 30 additions & 0 deletions Sources/System/Process/ProcessID.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

// FIXME(DO NOT MERGE): We need to find a way around this. We want to declare
// a typealias to a struct from a header, but don't want downstream to import
// Darwin or the whole header just for that.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out we should just import Darwin! Since System isn't an overlay, it doesn't need to reexport the module, and merely importing it won't pollute the client namespace with C junk. 🎉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use Darwin's types though? Also, can we do this with our CSystem module as well (which is needed for Linux, since libC isn't libSystem).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so -- if people only import System, they will still be able to refer to these types by one of their System typealiases (such as the ones in CInterop or RawValue). (They can't spell Darwin.foo unless they import it.)

CSystem is trickier, because it shouldn't be a public module, so we should continue to import it @_implementationOnly.

//
import Darwin
extension CInterop {
public typealias PID = Int32
public typealias ProcTaskInfo = proc_taskinfo // FIXME
public typealias RUsageInfo = rusage_info_current // FIXME
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the option to just go with the original type names here, but I think it's still nice to have these typealiases.

}

public struct ProcessID: RawRepresentable, Hashable, Codable {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that this is more of a Process (i.e. a system object identified by a pid) than a ProcessID - related APIs such as resource usage are really properties of the process more than the ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This struct is the handle to the process, it's very much like a file descriptor. It's not "safe" to assume it's still valid after a process is torn down, etc.

I've been thinking that a Process type would probably be more like an actor with interfaces for interprocess communication. Similarly with a proper File type (though that might be a moveonly buffered struct). ProcessID here is the lower-level systems model of that where we can communicate using descriptors or other forms of IPC.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think Process will be part of SystemPackage or layered on top? If it is part of SystemPackage is there a way to make a stub Process type so that this type could be Process.ID? I'm not sure if this will work for ABI stability (though I also can't say that it won't), but maybe we can create an enum Process that we then upgrade to an actor (or whatever).

/// The raw C process id.
@_alwaysEmitIntoClient
public let rawValue: CInterop.PID

/// Creates a strongly-typed process id from a raw C pid
@_alwaysEmitIntoClient
public init(rawValue: CInterop.PID) { self.rawValue = rawValue }

fileprivate init(_ rawValue: CInterop.PID) { self.init(rawValue: rawValue) }

}

extension ProcessID {
public static func current() -> ProcessID {
ProcessID(getpid())
}
}
159 changes: 159 additions & 0 deletions Sources/System/Process/ResourceUsageInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@

extension ProcessID {
public struct ResourceUsageInfo: RawRepresentable/*, Hashable, Codable*/ {
/// The raw C process id.
@_alwaysEmitIntoClient
public let rawValue: CInterop.RUsageInfo

/// Creates a strongly-typed process id from a raw C pid
@_alwaysEmitIntoClient
public init(rawValue: CInterop.RUsageInfo) { self.rawValue = rawValue }

fileprivate init(_ rawValue: CInterop.RUsageInfo) {
self.init(rawValue: rawValue)
}

fileprivate static var blank: ResourceUsageInfo {
ResourceUsageInfo(rusage_info_current())
}
}
}

// FIXME(DO NOT MERGE): system_foo wrappers for these and mocking
import CSystem
extension ProcessID {
public func getResourceUsageInfo() throws -> ResourceUsageInfo {
var current = ResourceUsageInfo.blank
try withUnsafeMutablePointer(to: &current) {
try $0.withMemoryRebound(to: rusage_info_t?.self, capacity: 1) {
guard 0 == proc_pid_rusage(self.rawValue, RUSAGE_INFO_CURRENT, $0) else {
throw Errno(rawValue: errno)
}
}
}
return current
}
}

// FIXME: docs or comments, the headers have none...
// FIXME: names
extension ProcessID.ResourceUsageInfo {
// FIXME: UUID proper type
public typealias UUID = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Foundation already has a wrapper for uuid_t -- perhaps we should investigate shrinking it into System.

UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)

/// `ri_uuid`: TBD
public var uuid: UUID { rawValue.ri_uuid }

/// `ri_user_time`: TBD
public var userTime: UInt64 { rawValue.ri_user_time }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should specify units for all these dimensioned quantities, ideally at the type system level. Should these time intervals return whatever we end up using for timespec_t?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the headers are not clear what even is here. It's like that ResourceUsageInfo will be split off from this PR eventually.


/// `ri_system_time`: TBD
public var systemTime: UInt64 { rawValue.ri_system_time }

/// `ri_pkg_idle_wkups`: TBD
public var pkgIdleWakeups: UInt64 { rawValue.ri_pkg_idle_wkups }

/// `ri_interrupt_wkups`: TBD
public var interruptWakeups: UInt64 { rawValue.ri_interrupt_wkups }

/// `ri_pageins`: TBD
public var pageIns: UInt64 { rawValue.ri_pageins }

/// `ri_wired_size`: TBD
public var wiredSize: UInt64 { rawValue.ri_wired_size }

/// `ri_resident_size`: TBD
public var residentSize: UInt64 { rawValue.ri_resident_size }

/// `ri_phys_footprint`: TBD
public var physicalFootprint: UInt64 { rawValue.ri_phys_footprint }

/// `ri_proc_start_abstime`: TBD
public var processStartAbsoluteTime: UInt64 { rawValue.ri_proc_start_abstime }

/// `ri_proc_exit_abstime`: TBD
public var processExitAbsoluteTime: UInt64 { rawValue.ri_proc_exit_abstime }

/// `ri_child_user_time`: TBD
public var childUserTime: UInt64 { rawValue.ri_child_user_time }

/// `ri_child_system_time`: TBD
public var childSystemTime: UInt64 { rawValue.ri_child_system_time }

/// `ri_child_pkg_idle_wkups`: TBD
public var childPkgIdleWakeups: UInt64 { rawValue.ri_child_pkg_idle_wkups }

/// `ri_child_interrupt_wkups`: TBD
public var childInterruptWakeups: UInt64 { rawValue.ri_child_interrupt_wkups }

/// `ri_child_pageins`: TBD
public var childPageIns: UInt64 { rawValue.ri_child_pageins }

/// `ri_child_elapsed_abstime`: TBD
public var childElapsedAbsoluteTime: UInt64 { rawValue.ri_child_elapsed_abstime }

/// `ri_diskio_bytesread`: TBD
public var diskIOBytesRead: UInt64 { rawValue.ri_diskio_bytesread }

/// `ri_diskio_byteswritten`: TBD
public var diskIOBytesWritten: UInt64 { rawValue.ri_diskio_byteswritten }

/// `ri_cpu_time_qos_default`: TBD
public var cpuTimeQOSDefault: UInt64 { rawValue.ri_cpu_time_qos_default }

/// `ri_cpu_time_qos_maintenance`: TBD
public var cpuTimeQOSMaintenance: UInt64 { rawValue.ri_cpu_time_qos_maintenance }

/// `ri_cpu_time_qos_background`: TBD
public var cpuTimeQOSBackground: UInt64 { rawValue.ri_cpu_time_qos_background }

/// `ri_cpu_time_qos_utility`: TBD
public var cpuTimeQOSUtility: UInt64 { rawValue.ri_cpu_time_qos_utility }

/// `ri_cpu_time_qos_legacy`: TBD
public var cpuTimeQOSLegacy: UInt64 { rawValue.ri_cpu_time_qos_legacy }

/// `ri_cpu_time_qos_user_initiated`: TBD
public var cpuTimeQOSUserInitiated: UInt64 { rawValue.ri_cpu_time_qos_user_initiated }

/// `ri_cpu_time_qos_user_interactive`: TBD
public var cpuTimeQOSUserInteractive: UInt64 { rawValue.ri_cpu_time_qos_user_interactive }

/// `ri_billed_system_time`: TBD
public var billedSystemTime: UInt64 { rawValue.ri_billed_system_time }

/// `ri_serviced_system_time`: TBD
public var servicedSystemTime: UInt64 { rawValue.ri_serviced_system_time }

/// `ri_logical_writes`: TBD
public var logicalWrites: UInt64 { rawValue.ri_logical_writes }

/// `ri_lifetime_max_phys_footprint`: TBD
public var lifetimeMaxPhysicalFootprint: UInt64 { rawValue.ri_lifetime_max_phys_footprint }

/// `ri_instructions`: TBD
public var instructions: UInt64 { rawValue.ri_instructions }

/// `ri_cycles`: TBD
public var cycles: UInt64 { rawValue.ri_cycles }

/// `ri_billed_energy`: TBD
public var billedEnergy: UInt64 { rawValue.ri_billed_energy }

/// `ri_serviced_energy`: TBD
public var servicedEnergy: UInt64 { rawValue.ri_serviced_energy }

/// `ri_interval_max_phys_footprint`: TBD
public var intervalMaxPhysicalFootprint: UInt64 { rawValue.ri_interval_max_phys_footprint }

/// `ri_runnable_time`: TBD
public var runnableTime: UInt64 { rawValue.ri_runnable_time }

/// `ri_flags`: TBD
public var flags: UInt64 { rawValue.ri_flags }

}


113 changes: 113 additions & 0 deletions Sources/System/Process/Signal.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@

public struct Signal: RawRepresentable, Hashable {
public var rawValue: CInt
public init(rawValue: CInt) { self.rawValue = rawValue }
fileprivate init(_ rawValue: CInt) { self.init(rawValue: rawValue) }
}

// FIXME(DO NOT MERGE): Migrate these to the constants.swift file
import Darwin

extension Signal {
#if os(Linux)
public static var unused: Signal { Signal(SIGUNUSED) }
#endif

// TODO: better names

/// SIGHUP (1): terminal line hangup (default behavior: terminate process)
public static var hangup: Signal { Signal(SIGHUP) }

/// SIGINT (2): interrupt program (default behavior: terminate process)
public static var interrupt: Signal { Signal(SIGINT) }

/// SIGQUIT (3): quit program (default behavior: create core image)
public static var quit: Signal { Signal(SIGQUIT) }

/// SIGILL (4): illegal instruction (default behavior: create core image)
public static var illegalInstruction: Signal { Signal(SIGILL) }

/// SIGTRAP (5): trace trap (default behavior: create core image)
public static var traceTrap: Signal { Signal(SIGTRAP) }

/// SIGABRT (6): abort program (formerly SIGIOT) (default behavior: create core image)
public static var abort: Signal { Signal(SIGABRT) }

/// SIGEMT (7): emulate instruction executed (default behavior: create core image)
public static var emulatorTrap: Signal { Signal(SIGEMT) }

/// SIGFPE (8): floating-point exception (default behavior: create core image)
public static var floatingPointException: Signal { Signal(SIGFPE) }

/// SIGKILL (9): kill program (default behavior: terminate process)
public static var kill: Signal { Signal(SIGKILL) }

/// SIGBUS (10): bus error (default behavior: create core image)
public static var busError: Signal { Signal(SIGBUS) }

/// SIGSEGV (11): segmentation violation (default behavior: create core image)
public static var segmentationViolation: Signal { Signal(SIGSEGV) }

/// SIGSYS (12): non-existent system call invoked (default behavior: create core image)
public static var unknownSystemCall: Signal { Signal(SIGSYS) }

/// SIGPIPE (13): write on a pipe with no reader (default behavior: terminate process)
public static var brokenPipe: Signal { Signal(SIGPIPE) }

/// SIGALRM (14): real-time timer expired (default behavior: terminate process)
public static var alarm: Signal { Signal(SIGALRM) }

/// SIGTERM (15): software termination signal (default behavior: terminate process)
public static var terminate: Signal { Signal(SIGTERM) }

/// SIGURG (16): urgent condition present on socket (default behavior: discard signal)
public static var urgentCondition: Signal { Signal(SIGURG) }

/// SIGSTOP (17): stop (cannot be caught or ignored) (default behavior: stop process)
public static var stop: Signal { Signal(SIGSTOP) }

/// SIGTSTP (18): stop signal generated from keyboard (default behavior: stop process)
public static var temporaryStop: Signal { Signal(SIGTSTP) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the T is short for "terminal" in this case; this is a job control thing, generated on C-z. It's the polite way to request that a process suspend itself, so what if:

Suggested change
public static var temporaryStop: Signal { Signal(SIGTSTP) }
public static var stopRequest: Signal { Signal(SIGTSTP) }

C.f. the terminationRequest suggestion above for SIGTERM.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think SIGSTOP is uncatchable so it's not a request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is SIGTSTP.

interactiveStop could be another name, or terminalStop, hopefully avoiding confusion with SIGSTOP.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh god, this is a great argument for not renaming them from what they are. I know SIGTSTP and SIGSTOP but stopRequest and interactiveStop really don't convey the meaning they need to convey IMHO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would terminalStop be clearer than interactiveStop? "Interactive stop" was from the GNU docs, and seemed a little more general than "terminal".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't think so. People who operate at this level know exactly what SIGTSTP is but they don't mind what Apple/POSIX/GNU document it as. So any wording you could make up here would lose the most crucial information which is that it is SIGTSTP.

Copy link
Member

@lorentey lorentey Feb 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling it SIGTSTP is a non-starter, I'm afraid. We need to follow the naming conventions we established for System.

Either terminalStop or interactiveStop looks fine to me. What term does POSIX use to explain this?

Copy link
Member

@lorentey lorentey Feb 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, POSIX calls it "Terminal stop signal". That sort of settles it for me.

Suggested change
public static var temporaryStop: Signal { Signal(SIGTSTP) }
public static var terminalStop: Signal { Signal(SIGTSTP) }

 


/// SIGCONT (19): continue after stop (default behavior: discard signal)
public static var `continue`: Signal { Signal(SIGCONT) }

/// SIGCHLD (20): child status has changed (default behavior: discard signal)
public static var childProcessStatusChange: Signal { Signal(SIGCHLD) }

/// SIGTTIN (21): background read attempted from control terminal (default behavior: stop process)
public static var backgroundReadFromControllingTerminal: Signal { Signal(SIGTTIN) }

/// SIGTTOU (22): background write attempted to control terminal (default behavior: stop process)
public static var backgroundWriteToControllingTerminal: Signal { Signal(SIGTTOU) }

/// SIGIO (23): I/O is possible on a descriptor (see fcntl(2)) (default behavior: discard signal)
public static var ioAvailable: Signal { Signal(SIGIO) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the io prefix weird? Not that I can think of a better suggestion. readyForIO and readyCondition (c.f. urgentCondition) both seem silly.


/// SIGXCPU (24): cpu time limit exceeded (see setrlimit(2)) (default behavior: terminate process)
public static var cpuLimitExceeded: Signal { Signal(SIGXCPU) }

/// SIGXFSZ (25): file size limit exceeded (see setrlimit(2)) (default behavior: terminate process)
public static var fileSizeLimitExceeded: Signal { Signal(SIGXFSZ) }

/// SIGVTALRM (26): virtual time alarm (see setitimer(2)) (default behavior: terminate process)
public static var virtualAlarm: Signal { Signal(SIGVTALRM) }

/// SIGPROF (27): profiling timer alarm (see setitimer(2)) (default behavior: terminate process)
public static var profilingAlarm: Signal { Signal(SIGPROF) }

/// SIGWINCH (28): Window size change (default behavior: discard signal)
public static var windowSizeChange: Signal { Signal(SIGWINCH) }

/// SIGINFO (29): status request from keyboard (default behavior: discard signal)
public static var info: Signal { Signal(SIGINFO) }

/// SIGUSR1 (30): User defined signal 1 (default behavior: terminate process)
public static var user1: Signal { Signal(SIGUSR1) }

/// SIGUSR2 (31): User defined signal 2 (default behavior: terminate process)
public static var user2: Signal { Signal(SIGUSR2) }

}

// TODO: unavailable renamed
Loading