diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index e427980..166c2b2 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -1,10 +1,11 @@ # This workflow will build a golang project -name: Go +name: CI on: push: branches: [ "main" ] + pull_request: branches: [ "main" ] @@ -13,7 +14,7 @@ on: jobs: - build: + run-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -23,5 +24,9 @@ jobs: with: go-version: '1.23' - - name: Test - run: make test-all + # necessary for testing proxy/Process swapping + - name: Create simple-responder + run: make simple-responder + + - name: Test all + run: make test-all \ No newline at end of file diff --git a/proxy/process.go b/proxy/process.go index ede239c..db0bc91 100644 --- a/proxy/process.go +++ b/proxy/process.go @@ -133,9 +133,24 @@ func (p *Process) start() error { return nil } + // There is the possibility of a hard to replicate race condition where + // curState *WAS* StateStopped but by the time we get to the p.stateMutex.Lock() + // below, it's value has changed! + p.stateMutex.Lock() defer p.stateMutex.Unlock() + // with the exclusive lock, check if p.state is StateStopped, which is the only valid state + // to transition from to StateReady + + if p.state != StateStopped { + if p.state == StateReady { + return nil + } else { + return fmt.Errorf("start() can not proceed expected StateReady but process is in %v", p.state) + } + } + if err := p.setState(StateStarting); err != nil { return err } diff --git a/proxy/process_test.go b/proxy/process_test.go index 0d7d3f8..02ba913 100644 --- a/proxy/process_test.go +++ b/proxy/process_test.go @@ -169,6 +169,8 @@ func TestProcess_LowTTLValue(t *testing.T) { } // issue #19 +// This test makes sure using Process.Stop() does not affect pending HTTP +// requests. All HTTP requests in this test should complete successfully. func TestProcess_HTTPRequestsHaveTimeToFinish(t *testing.T) { if testing.Short() { t.Skip("skipping slow test") @@ -192,8 +194,9 @@ func TestProcess_HTTPRequestsHaveTimeToFinish(t *testing.T) { wg.Add(1) go func(key string) { defer wg.Done() - // send a request that should take 5 * 200ms (1 second) to complete - req := httptest.NewRequest("GET", fmt.Sprintf("/slow-respond?echo=%s&delay=200ms", key), nil) + // send a request where simple-responder is will wait 300ms before responding + // this will simulate an in-progress request. + req := httptest.NewRequest("GET", fmt.Sprintf("/slow-respond?echo=%s&delay=300ms", key), nil) w := httptest.NewRecorder() process.ProxyRequest(w, req) @@ -209,9 +212,9 @@ func TestProcess_HTTPRequestsHaveTimeToFinish(t *testing.T) { }(key) } - // stop the requests in the middle + // Stop the process while requests are still being processed go func() { - <-time.After(500 * time.Millisecond) + <-time.After(150 * time.Millisecond) process.Stop() }()