Skip to content

Commit 646d285

Browse files
ianlancetaylorgopherbot
authored andcommitted
os: simplify Process.Release
Consolidate release/deactivation into a single doRelease method. It needs to check GOOS for backward compatibility, but it's simpler to keep all the logic in one place. Change-Id: I242eb084d44d2682f862a8fbf55c410fb8c53358 Reviewed-on: https://go-review.googlesource.com/c/go/+/638580 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent bdbc5ca commit 646d285

File tree

5 files changed

+61
-117
lines changed

5 files changed

+61
-117
lines changed

src/os/exec.go

Lines changed: 48 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -176,40 +176,6 @@ func (p *Process) handleTransientRelease() {
176176
p.handle.release()
177177
}
178178

179-
// Drop the Process' persistent reference on the handle, deactivating future
180-
// Wait/Signal calls with the passed reason.
181-
//
182-
// Returns the status prior to this call. If this is not statusOK, then the
183-
// reference was not dropped or status changed.
184-
func (p *Process) handlePersistentRelease(reason processStatus) processStatus {
185-
if p.handle == nil {
186-
panic("handlePersistentRelease called in invalid mode")
187-
}
188-
189-
for {
190-
state := p.state.Load()
191-
status := processStatus(state)
192-
if status != statusOK {
193-
// Both Release and successful Wait will drop the
194-
// Process' persistent reference on the handle. We
195-
// can't allow concurrent calls to drop the reference
196-
// twice, so we use the status as a guard to ensure the
197-
// reference is dropped exactly once.
198-
return status
199-
}
200-
if !p.state.CompareAndSwap(state, uint32(reason)) {
201-
continue
202-
}
203-
204-
// No need for more cleanup.
205-
p.cleanup.Stop()
206-
207-
p.handle.release()
208-
209-
return status
210-
}
211-
}
212-
213179
func (p *Process) pidStatus() processStatus {
214180
if p.handle != nil {
215181
panic("pidStatus called in invalid mode")
@@ -218,22 +184,6 @@ func (p *Process) pidStatus() processStatus {
218184
return processStatus(p.state.Load())
219185
}
220186

221-
func (p *Process) pidDeactivate(reason processStatus) {
222-
if p.handle != nil {
223-
panic("pidDeactivate called in invalid mode")
224-
}
225-
226-
// Both Release and successful Wait will deactivate the PID. Only one
227-
// of those should win, so nothing left to do here if the compare
228-
// fails.
229-
//
230-
// N.B. This means that results can be inconsistent. e.g., with a
231-
// racing Release and Wait, Wait may successfully wait on the process,
232-
// returning the wait status, while future calls error with "process
233-
// released" rather than "process done".
234-
p.state.CompareAndSwap(0, uint32(reason))
235-
}
236-
237187
// ProcAttr holds the attributes that will be applied to a new process
238188
// started by StartProcess.
239189
type ProcAttr struct {
@@ -310,23 +260,54 @@ func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error)
310260
// rendering it unusable in the future.
311261
// Release only needs to be called if [Process.Wait] is not.
312262
func (p *Process) Release() error {
313-
// Note to future authors: the Release API is cursed.
314-
//
315-
// On Unix and Plan 9, Release sets p.Pid = -1. This is the only part of the
316-
// Process API that is not thread-safe, but it can't be changed now.
317-
//
318-
// On Windows, Release does _not_ modify p.Pid.
319-
//
320-
// On Windows, Wait calls Release after successfully waiting to
321-
// proactively clean up resources.
322-
//
323-
// On Unix and Plan 9, Wait also proactively cleans up resources, but
324-
// can not call Release, as Wait does not set p.Pid = -1.
325-
//
326-
// On Unix and Plan 9, calling Release a second time has no effect.
327-
//
328-
// On Windows, calling Release a second time returns EINVAL.
329-
return p.release()
263+
// Unfortunately, for historical reasons, on systems other
264+
// than Windows, Release sets the Pid field to -1.
265+
// This causes the race detector to report a problem
266+
// on concurrent calls to Release, but we can't change it now.
267+
if runtime.GOOS != "windows" {
268+
p.Pid = -1
269+
}
270+
271+
oldStatus := p.doRelease(statusReleased)
272+
273+
// For backward compatibility, on Windows only,
274+
// we return EINVAL on a second call to Release.
275+
if runtime.GOOS == "windows" {
276+
if oldStatus == statusReleased {
277+
return syscall.EINVAL
278+
}
279+
}
280+
281+
return nil
282+
}
283+
284+
// doRelease releases a [Process], setting the status to newStatus.
285+
// If the previous status is not statusOK, this does nothing.
286+
// It returns the previous status.
287+
func (p *Process) doRelease(newStatus processStatus) processStatus {
288+
for {
289+
state := p.state.Load()
290+
oldStatus := processStatus(state)
291+
if oldStatus != statusOK {
292+
return oldStatus
293+
}
294+
295+
if !p.state.CompareAndSwap(state, uint32(newStatus)) {
296+
continue
297+
}
298+
299+
// We have successfully released the Process.
300+
// If it has a handle, release the reference we
301+
// created in newHandleProcess.
302+
if p.handle != nil {
303+
// No need for more cleanup.
304+
p.cleanup.Stop()
305+
306+
p.handle.release()
307+
}
308+
309+
return statusOK
310+
}
330311
}
331312

332313
// Kill causes the [Process] to exit immediately. Kill does not wait until

src/os/exec_plan9.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,14 @@ func (p *Process) wait() (ps *ProcessState, err error) {
8080
return nil, NewSyscallError("wait", err)
8181
}
8282

83-
p.pidDeactivate(statusDone)
83+
p.doRelease(statusDone)
8484
ps = &ProcessState{
8585
pid: waitmsg.Pid,
8686
status: &waitmsg,
8787
}
8888
return ps, nil
8989
}
9090

91-
func (p *Process) release() error {
92-
p.Pid = -1
93-
94-
// Just mark the PID unusable.
95-
p.pidDeactivate(statusReleased)
96-
97-
return nil
98-
}
99-
10091
func findProcess(pid int) (p *Process, err error) {
10192
// NOOP for Plan 9.
10293
return newPIDProcess(pid), nil

src/os/exec_unix.go

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (p *Process) pidWait() (*ProcessState, error) {
4949
if ready {
5050
// Mark the process done now, before the call to Wait4,
5151
// so that Process.pidSignal will not send a signal.
52-
p.pidDeactivate(statusDone)
52+
p.doRelease(statusDone)
5353
// Acquire a write lock on sigMu to wait for any
5454
// active call to the signal method to complete.
5555
p.sigMu.Lock()
@@ -66,7 +66,7 @@ func (p *Process) pidWait() (*ProcessState, error) {
6666
if err != nil {
6767
return nil, NewSyscallError("wait", err)
6868
}
69-
p.pidDeactivate(statusDone)
69+
p.doRelease(statusDone)
7070
return &ProcessState{
7171
pid: pid1,
7272
status: status,
@@ -118,27 +118,6 @@ func convertESRCH(err error) error {
118118
return err
119119
}
120120

121-
func (p *Process) release() error {
122-
// We clear the Pid field only for API compatibility. On Unix, Release
123-
// has always set Pid to -1. Internally, the implementation relies
124-
// solely on statusReleased to determine that the Process is released.
125-
p.Pid = pidReleased
126-
127-
if p.handle != nil {
128-
// Drop the Process' reference and mark handle unusable for
129-
// future calls.
130-
//
131-
// Ignore the return value: we don't care if this was a no-op
132-
// racing with Wait, or a double Release.
133-
p.handlePersistentRelease(statusReleased)
134-
} else {
135-
// Just mark the PID unusable.
136-
p.pidDeactivate(statusReleased)
137-
}
138-
139-
return nil
140-
}
141-
142121
func findProcess(pid int) (p *Process, err error) {
143122
h, err := pidfdFind(pid)
144123
if err == ErrProcessDone {

src/os/exec_windows.go

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ func (p *Process) wait() (ps *ProcessState, err error) {
4444
if e != nil {
4545
return nil, NewSyscallError("GetProcessTimes", e)
4646
}
47-
defer p.Release()
47+
48+
// For compatibility we use statusReleased here rather
49+
// than statusDone.
50+
p.doRelease(statusReleased)
51+
4852
return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
4953
}
5054

@@ -73,19 +77,6 @@ func (p *Process) signal(sig Signal) error {
7377
return syscall.Errno(syscall.EWINDOWS)
7478
}
7579

76-
func (p *Process) release() error {
77-
// Drop the Process' reference and mark handle unusable for
78-
// future calls.
79-
//
80-
// The API on Windows expects EINVAL if Release is called multiple
81-
// times.
82-
if old := p.handlePersistentRelease(statusReleased); old == statusReleased {
83-
return syscall.EINVAL
84-
}
85-
86-
return nil
87-
}
88-
8980
func (ph *processHandle) closeHandle() {
9081
syscall.CloseHandle(syscall.Handle(ph.handle))
9182
}

src/os/pidfd_linux.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ func (p *Process) pidfdWait() (*ProcessState, error) {
108108
if err != nil {
109109
return nil, NewSyscallError("waitid", err)
110110
}
111-
// Release the Process' handle reference, in addition to the reference
112-
// we took above.
113-
p.handlePersistentRelease(statusDone)
111+
112+
// Update the Process status to statusDone.
113+
// This also releases a reference to the handle.
114+
p.doRelease(statusDone)
115+
114116
return &ProcessState{
115117
pid: int(info.Pid),
116118
status: info.WaitStatus(),

0 commit comments

Comments
 (0)