Improve Continuous integration and fix concurrency bugs (#66)

- improvements to the continuous GH actions
- fix edge case concurrency bugs with Process.start() and state transitions discovered setting up CI.
This commit is contained in:
Benson Wong
2025-03-11 10:39:14 -07:00
committed by GitHub
parent eeb72297f7
commit 9b2ed244e2
3 changed files with 31 additions and 8 deletions

View File

@@ -1,10 +1,11 @@
# This workflow will build a golang project # This workflow will build a golang project
name: Go name: CI
on: on:
push: push:
branches: [ "main" ] branches: [ "main" ]
pull_request: pull_request:
branches: [ "main" ] branches: [ "main" ]
@@ -13,7 +14,7 @@ on:
jobs: jobs:
build: run-tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -23,5 +24,9 @@ jobs:
with: with:
go-version: '1.23' go-version: '1.23'
- name: Test # necessary for testing proxy/Process swapping
- name: Create simple-responder
run: make simple-responder
- name: Test all
run: make test-all run: make test-all

View File

@@ -133,9 +133,24 @@ func (p *Process) start() error {
return nil 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() p.stateMutex.Lock()
defer p.stateMutex.Unlock() 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 { if err := p.setState(StateStarting); err != nil {
return err return err
} }

View File

@@ -169,6 +169,8 @@ func TestProcess_LowTTLValue(t *testing.T) {
} }
// issue #19 // 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) { func TestProcess_HTTPRequestsHaveTimeToFinish(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping slow test") t.Skip("skipping slow test")
@@ -192,8 +194,9 @@ func TestProcess_HTTPRequestsHaveTimeToFinish(t *testing.T) {
wg.Add(1) wg.Add(1)
go func(key string) { go func(key string) {
defer wg.Done() defer wg.Done()
// send a request that should take 5 * 200ms (1 second) to complete // send a request where simple-responder is will wait 300ms before responding
req := httptest.NewRequest("GET", fmt.Sprintf("/slow-respond?echo=%s&delay=200ms", key), nil) // this will simulate an in-progress request.
req := httptest.NewRequest("GET", fmt.Sprintf("/slow-respond?echo=%s&delay=300ms", key), nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
process.ProxyRequest(w, req) process.ProxyRequest(w, req)
@@ -209,9 +212,9 @@ func TestProcess_HTTPRequestsHaveTimeToFinish(t *testing.T) {
}(key) }(key)
} }
// stop the requests in the middle // Stop the process while requests are still being processed
go func() { go func() {
<-time.After(500 * time.Millisecond) <-time.After(150 * time.Millisecond)
process.Stop() process.Stop()
}() }()