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 }

}


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

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: Singal { 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 illegalInsruction: 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 segfault: Signal { Signal(SIGSEGV) }
Copy link
Member

Choose a reason for hiding this comment

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

Segfault is a colloquialism; segmentationFault or segmentationViolation seems like a better fit.

Suggested change
public static var segfault: Signal { Signal(SIGSEGV) }
public static var segmentationViolation: Signal { Signal(SIGSEGV) }

Copy link
Member

Choose a reason for hiding this comment

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

not super how I feel about this. We're not using segmentation anymore but paging, so spelling "segfault" out may not make it easier to find?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having an unavailable-renamed entry for SIGSEGV would hopefully help point to whatever name we choose.

Copy link
Member

Choose a reason for hiding this comment

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

That's true. But segmentationViolation is still not what this will be sent for so I'd be inclined to leave the as a "term of art".

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.

Yeah, this is sort of like hangup in that the name has somehow outlived its original meaning, which is now a complete anachronism. SIGSEGV is defined as "segmentation violation" in the man pages, so at least this expansion isn't likely to confuse anyone.

@weissi, what would you propose as a replacement? Note that System is supposed to follow the Swift naming conventions, so the original arbitrary abbreviations need to be replaced, no matter how well known.

Copy link
Member

Choose a reason for hiding this comment

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

FWIW, POSIX describes SIGSEGV as "Invalid memory reference". Would we be willing to name it invalidMemoryReference?

SEGV is such an (in)famous signal, it may be worth spending a term-of-art card on segmentationViolation, no matter how far it is from the current meaning...

Copy link
Member

Choose a reason for hiding this comment

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

invalidMemoryReference is IMHO much better than segmentationViolation. Both names however aren't as good as SIGSEGV or sigsegv IMHO. If you want to program your operating system directly, I think any translation of names is counterproductive and will lead to avoidable bugs, imprecise reviews, etc.


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

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

Choose a reason for hiding this comment

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

Suggested change
public static var pipe: Signal { Signal(SIGPIPE) }
public static var brokenPipe: Signal { Signal(SIGPIPE) }

Copy link
Member

Choose a reason for hiding this comment

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

hmm, you can also get them on sockets...

Copy link
Member

Choose a reason for hiding this comment

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

Oh that's interesting. 🤔

Broken pipe is what psignal prints on SIGPIPE, which is why I suggested it. It has the advantage that it evokes the original name but also tries to clarify it a little.

Should we use that, use pipe, or should we try to find a more exact term?

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, we imported EPIPE as Errno.brokenPipe. I think we should probably use the same name here -- especially since it looks like the error is returned in the exact same situations as when the signal is sent.

[EPIPE]
Broken pipe. A write was attempted on a socket, pipe, or FIFO for which there is no process to read the data.


/// 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 urgent: 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 childProcessTerminate: Signal { Signal(SIGCHLD) }

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

/// SIGTTOU (22): background write attempted to control terminal (default behavior: stop process)
public static var ttyOut: 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