Skip to content

Commit 823c5da

Browse files
committed
Process: Match Darwin's handling for various properties and methods
- .processIdentifier is 0 before launch and retains the PID value after termination. - .terminationStatus, .terminationReason: Add precondition that the task has run and exited. - interrupt(), termintate(): Add precondition that the task has started. - suspend(), resume(): Remove checks that process is running and match Darwin's suspendCount increment/decrement.
1 parent 05432c6 commit 823c5da

File tree

2 files changed

+59
-30
lines changed

2 files changed

+59
-30
lines changed

Foundation/Process.swift

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,12 @@ open class Process: NSObject {
355355
} while ( (waitResult == -1) && (errno == EINTR) )
356356

357357
if WIFSIGNALED(exitCode) {
358-
process.terminationStatus = WTERMSIG(exitCode)
359-
process.terminationReason = .uncaughtSignal
358+
process._terminationStatus = WTERMSIG(exitCode)
359+
process._terminationReason = .uncaughtSignal
360360
} else {
361361
assert(WIFEXITED(exitCode))
362-
process.terminationStatus = WEXITSTATUS(exitCode)
363-
process.terminationReason = .exit
362+
process._terminationStatus = WEXITSTATUS(exitCode)
363+
process._terminationReason = .exit
364364
}
365365

366366
// If a termination handler has been set, invoke it on a background thread
@@ -374,7 +374,6 @@ open class Process: NSObject {
374374

375375
// Set the running flag to false
376376
process.isRunning = false
377-
process.processIdentifier = -1
378377

379378
// Invalidate the source and wake up the run loop, if it's available
380379

@@ -508,51 +507,58 @@ open class Process: NSObject {
508507
}
509508

510509
open func interrupt() {
511-
if isRunning && processIdentifier > 0 {
512-
kill(processIdentifier, SIGINT)
513-
}
510+
precondition(hasStarted, "task not launched")
511+
kill(processIdentifier, SIGINT)
514512
}
515513

516514
open func terminate() {
517-
if isRunning && processIdentifier > 0 {
518-
kill(processIdentifier, SIGTERM)
519-
}
515+
precondition(hasStarted, "task not launched")
516+
kill(processIdentifier, SIGTERM)
520517
}
521518

522519
// Every suspend() has to be balanced with a resume() so keep a count of both.
523520
private var suspendCount = 0
524521

525522
open func suspend() -> Bool {
526-
guard isRunning else {
523+
if kill(processIdentifier, SIGSTOP) == 0 {
524+
suspendCount += 1
525+
return true
526+
} else {
527527
return false
528528
}
529-
530-
suspendCount += 1
531-
if suspendCount == 1, processIdentifier > 0 {
532-
kill(processIdentifier, SIGSTOP)
533-
}
534-
return true
535529
}
536530

537531
open func resume() -> Bool {
538-
guard isRunning else {
539-
return true
532+
var success = true
533+
if suspendCount == 1 {
534+
success = kill(processIdentifier, SIGCONT) == 0
540535
}
541-
542-
suspendCount -= 1
543-
if suspendCount == 0, processIdentifier > 0 {
544-
kill(processIdentifier, SIGCONT)
536+
if success {
537+
suspendCount -= 1
545538
}
546-
return true
539+
return success
547540
}
548541

549542
// status
550-
open private(set) var processIdentifier: Int32 = -1
543+
open private(set) var processIdentifier: Int32 = 0
551544
open private(set) var isRunning: Bool = false
552-
553-
open private(set) var terminationStatus: Int32 = 0
554-
open private(set) var terminationReason: TerminationReason = .exit
555-
545+
private var hasStarted: Bool { return processIdentifier > 0 }
546+
private var hasFinished: Bool { return !isRunning && processIdentifier > 0 }
547+
548+
private var _terminationStatus: Int32 = 0
549+
public var terminationStatus: Int32 {
550+
precondition(hasStarted, "task not launched")
551+
precondition(hasFinished, "task still running")
552+
return _terminationStatus
553+
}
554+
555+
private var _terminationReason: TerminationReason = .exit
556+
public var terminationReason: TerminationReason {
557+
precondition(hasStarted, "task not launched")
558+
precondition(hasFinished, "task still running")
559+
return _terminationReason
560+
}
561+
556562
/*
557563
A block to be invoked when the process underlying the Process terminates. Setting the block to nil is valid, and stops the previous block from being invoked, as long as it hasn't started in any way. The Process is passed as the argument to the block so the block does not have to capture, and thus retain, it. The block is copied when set. Only one termination handler block can be set at any time. The execution context in which the block is invoked is undefined. If the Process has already finished, the block is executed immediately/soon (not necessarily on the current thread). If a terminationHandler is set on an Process, the ProcessDidTerminateNotification notification is not posted for that process. Also note that -waitUntilExit won't wait until the terminationHandler has been fully executed. You cannot use this property in a concrete subclass of Process which hasn't been updated to include an implementation of the storage and use of it.
558564
*/

TestFoundation/TestProcess.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class TestProcess : XCTestCase {
2929
("test_no_environment", test_no_environment),
3030
("test_custom_environment", test_custom_environment),
3131
("test_run", test_run),
32+
("test_preStartEndState", test_preStartEndState),
3233
("test_interrupt", test_interrupt),
3334
("test_terminate", test_terminate),
3435
("test_suspend_resume", test_suspend_resume),
@@ -388,6 +389,28 @@ class TestProcess : XCTestCase {
388389
fm.changeCurrentDirectoryPath(cwd)
389390
}
390391

392+
func test_preStartEndState() {
393+
let process = Process()
394+
XCTAssertNil(process.executableURL)
395+
XCTAssertNotNil(process.currentDirectoryURL)
396+
XCTAssertNil(process.arguments)
397+
XCTAssertNil(process.environment)
398+
XCTAssertFalse(process.isRunning)
399+
XCTAssertEqual(process.processIdentifier, 0)
400+
XCTAssertEqual(process.qualityOfService, .default)
401+
402+
process.executableURL = URL(fileURLWithPath: "/bin/cat", isDirectory: false)
403+
_ = try? process.run()
404+
XCTAssertTrue(process.isRunning)
405+
XCTAssertTrue(process.processIdentifier > 0)
406+
process.terminate()
407+
process.waitUntilExit()
408+
XCTAssertFalse(process.isRunning)
409+
XCTAssertTrue(process.processIdentifier > 0)
410+
XCTAssertEqual(process.terminationReason, .uncaughtSignal)
411+
XCTAssertEqual(process.terminationStatus, SIGTERM)
412+
}
413+
391414
func test_interrupt() {
392415
let helper = _SignalHelperRunner()
393416
do {

0 commit comments

Comments
 (0)