-
-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: ensure executor doesn't deadlock when closure errors
When running 'gomod2nix' on in my project, the 'gomod2nix import' was failing for every import. I have more imports than the default maxJobs. This caused a deadlock and the program never finished. This is because in the erroring case, we send to the errChan, which is a blocking channel. If this blocks then the defers are never called, most importantly the `defer` which pulls an entry off the semaphore (e.guard). This means once the erroring work functions exceeds the numWorkers, we will block trying to acquire the semaphore when we call .Add with more work. We never get to the point where we call .Wait(), which would drain the errChan becuase we are blocked on the semaphore whilst we are still generating work. This change moves the semaphore acquire to within the goroutines themselves. This alters the behaviour in that we now will start as many goroutines as we have work items, but the work they do will still be gated by the semaphore. This is reasonable behaviour: goroutines are cheap, in general this package is useful if the work the functions are doing is expensive not the goroutine creation itself. The work still is guarded by the semaphore. There is also a regression test added and in passing, the spelling of Parallel is corrected.
- Loading branch information
1 parent
e8f299a
commit 5d38709
Showing
4 changed files
with
46 additions
and
10 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
result* | ||
/tests/*/gomod2nix.toml | ||
/tests/*/go.mod | ||
.idea |
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,36 @@ | ||
package lib | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
"time" | ||
) | ||
|
||
// TestParallelExecutor_fnAlwaysErrors ensures that the executor does not block | ||
// forever when there are more erroring functions than workers. This is a | ||
// regression test. | ||
func TestParallelExecutor_fnAlwaysErrors(t *testing.T) { | ||
const maxWorkers = 1 | ||
executor := NewParallelExecutor(1) | ||
|
||
for i := 0; i < maxWorkers+1; i++ { | ||
executor.Add(func() error { | ||
return errors.New("testerror") | ||
}) | ||
} | ||
|
||
errCh := make(chan error) | ||
go func() { | ||
defer close(errCh) | ||
errCh <- executor.Wait() | ||
}() | ||
|
||
select { | ||
case err := <-errCh: | ||
if err == nil { | ||
t.Error("Expected error, got nil") | ||
} | ||
case <-time.After(10 * time.Second): | ||
t.Error("Timed out waiting for executor to finish: deadlock") | ||
} | ||
} |