mirror of
https://github.com/peco/peco.git
synced 2026-09-10 07:16:29 -04:00
Fix possible race in signal handler
This commit is contained in:
parent
deee60fad0
commit
78d7e4a5d8
|
|
@ -39,6 +39,7 @@ func New(h SigReceivedHandler, sigs ...os.Signal) *Handler {
|
|||
|
||||
func (h *Handler) Loop(ctx context.Context, cancel func()) error {
|
||||
defer cancel()
|
||||
defer signal.Stop(h.sigCh)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
|
|
|||
100
sig/sig_test.go
Normal file
100
sig/sig_test.go
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package sig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestLoopContextCancel verifies that Loop exits when the context is cancelled
|
||||
// and that signal.Stop is called (the channel no longer receives signals).
|
||||
func TestLoopContextCancel(t *testing.T) {
|
||||
var received os.Signal
|
||||
h := New(SigReceivedHandlerFunc(func(sig os.Signal) {
|
||||
received = sig
|
||||
}), syscall.SIGUSR1)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- h.Loop(ctx, cancel)
|
||||
}()
|
||||
|
||||
// Cancel the context to make Loop exit
|
||||
cancel()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Loop did not exit after context cancellation")
|
||||
}
|
||||
|
||||
// After Loop returns, the signal channel should be deregistered.
|
||||
// Sending SIGUSR1 to ourselves should NOT be received by the handler.
|
||||
require.Nil(t, received, "handler should not have been called")
|
||||
|
||||
// Verify the channel is stopped: send a signal and confirm it doesn't
|
||||
// arrive on the (now-stopped) channel.
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
select {
|
||||
case _, ok := <-h.sigCh:
|
||||
if ok {
|
||||
t.Fatal("signal was delivered to channel after Loop returned — signal.Stop was not called")
|
||||
}
|
||||
default:
|
||||
// Channel is empty — signal.Stop worked correctly
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoopSignalReceived verifies that Loop calls the handler and exits
|
||||
// when a signal is received, and that signal.Stop is called afterward.
|
||||
func TestLoopSignalReceived(t *testing.T) {
|
||||
var received os.Signal
|
||||
h := New(SigReceivedHandlerFunc(func(sig os.Signal) {
|
||||
received = sig
|
||||
}), syscall.SIGUSR1)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- h.Loop(ctx, cancel)
|
||||
}()
|
||||
|
||||
// Give Loop a moment to start
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Send SIGUSR1 to ourselves
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Loop did not exit after signal")
|
||||
}
|
||||
|
||||
require.Equal(t, syscall.SIGUSR1, received, "handler should have received SIGUSR1")
|
||||
|
||||
// After Loop exits, send another signal — it should NOT be delivered
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
select {
|
||||
case _, ok := <-h.sigCh:
|
||||
if ok {
|
||||
t.Fatal("signal was delivered to channel after Loop returned — signal.Stop was not called")
|
||||
}
|
||||
default:
|
||||
// Channel is empty — signal.Stop worked correctly
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue