-
Notifications
You must be signed in to change notification settings - Fork 297
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor Done() to use a broadcasted Signal type for future features. (…
…#984) This creates a new public struct type `ShutdownSignal` and moves broadcast of operating system signals to a standalone type in it's own source file. This allows for the cleaner expansion of signaling features. Co-authored-by: Abhinav Gupta <mail@abhinavg.net> Co-authored-by: Sung Yoon Whang <sungyoonwhang@gmail.com>
- Loading branch information
1 parent
09bba9b
commit fd5fd36
Showing
5 changed files
with
186 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright (c) 2022 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPSignalE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
package fx | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/signal" | ||
"sync" | ||
) | ||
|
||
// ShutdownSignal is a signal that caused the application to exit. | ||
type ShutdownSignal struct { | ||
Signal os.Signal | ||
} | ||
|
||
// String will render a ShutdownSignal type as a string suitable for printing. | ||
func (sig ShutdownSignal) String() string { | ||
return fmt.Sprintf("%v", sig.Signal) | ||
} | ||
|
||
func newSignalReceivers() signalReceivers { | ||
return signalReceivers{notify: signal.Notify} | ||
} | ||
|
||
type signalReceivers struct { | ||
m sync.Mutex | ||
last *ShutdownSignal | ||
done []chan os.Signal | ||
notify func(c chan<- os.Signal, sig ...os.Signal) | ||
} | ||
|
||
func (recv *signalReceivers) Done() <-chan os.Signal { | ||
recv.m.Lock() | ||
defer recv.m.Unlock() | ||
|
||
ch := make(chan os.Signal, 1) | ||
|
||
// If we had received a signal prior to the call of done, send it's | ||
// os.Signal to the new channel. | ||
// However we still want to have the operating system notify signals to this | ||
// channel should the application receive another. | ||
if recv.last != nil { | ||
ch <- recv.last.Signal | ||
} | ||
|
||
recv.notify(ch, os.Interrupt, _sigINT, _sigTERM) | ||
recv.done = append(recv.done, ch) | ||
return ch | ||
} | ||
|
||
func (recv *signalReceivers) Broadcast(signal ShutdownSignal) error { | ||
recv.m.Lock() | ||
defer recv.m.Unlock() | ||
recv.last = &signal | ||
|
||
channels, unsent := recv.broadcastDone(signal) | ||
|
||
if unsent != 0 { | ||
return &unsentSignalError{ | ||
Signal: signal, | ||
Total: channels, | ||
Unsent: unsent, | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (recv *signalReceivers) broadcastDone(signal ShutdownSignal) (int, int) { | ||
var unsent int | ||
|
||
for _, reader := range recv.done { | ||
select { | ||
case reader <- signal.Signal: | ||
default: | ||
unsent++ | ||
} | ||
} | ||
|
||
return len(recv.done), unsent | ||
} | ||
|
||
type unsentSignalError struct { | ||
Signal ShutdownSignal | ||
Unsent int | ||
Total int | ||
} | ||
|
||
func (err *unsentSignalError) Error() string { | ||
return fmt.Sprintf( | ||
"send %v signal: %v/%v channels are blocked", | ||
err.Signal, | ||
err.Unsent, | ||
err.Total, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Copyright (c) 2022 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPSignalE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
package fx | ||
|
||
import ( | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"syscall" | ||
"testing" | ||
) | ||
|
||
func assertUnsentSignalError( | ||
t *testing.T, | ||
err error, | ||
expected *unsentSignalError, | ||
) { | ||
t.Helper() | ||
|
||
actual := new(unsentSignalError) | ||
|
||
assert.ErrorContains(t, err, "channels are blocked") | ||
if assert.ErrorAs(t, err, &actual, "is unsentSignalError") { | ||
assert.Equal(t, expected, actual) | ||
} | ||
} | ||
|
||
func TestSignal(t *testing.T) { | ||
t.Parallel() | ||
recv := newSignalReceivers() | ||
a := recv.Done() | ||
_ = recv.Done() // we never listen on this | ||
|
||
expected := ShutdownSignal{ | ||
Signal: syscall.SIGTERM, | ||
} | ||
|
||
require.NoError(t, recv.Broadcast(expected), "first broadcast should succeed") | ||
|
||
assertUnsentSignalError(t, recv.Broadcast(expected), &unsentSignalError{ | ||
Signal: expected, | ||
Total: 2, | ||
Unsent: 2, | ||
}) | ||
|
||
assert.Equal(t, expected.Signal, <-a) | ||
assert.Equal(t, expected.Signal, <-recv.Done(), "expect cached signal") | ||
} |