fix service deadlock after failed build setup (#4506)

This commit is contained in:
Vasyl Zuziak
2026-08-07 21:13:47 -04:00
committed by GitHub
parent bb06391390
commit 5e52b56fc7
2 changed files with 109 additions and 2 deletions
+7 -2
View File
@@ -639,6 +639,12 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
}
activeBuild := service.createActiveBuild(key)
shouldDestroyActiveBuild := true
defer func() {
if shouldDestroyActiveBuild {
service.destroyActiveBuild(key)
}
}()
hasOnEndCallbacks := false
if plugins, ok := request["plugins"]; ok {
@@ -769,6 +775,7 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
// Keep the build alive until "dispose" has been called
activeBuild.disposeWaitGroup.Add(1)
activeBuild.ctx = ctx
shouldDestroyActiveBuild = false
return encodePacket(packet{
id: id,
@@ -782,8 +789,6 @@ func (service *serviceType) handleBuildRequest(id uint32, request map[string]int
result := api.Build(options)
response := resultToResponse(result)
service.destroyActiveBuild(key)
return encodePacket(packet{
id: id,
value: response,
+102
View File
@@ -0,0 +1,102 @@
package main
import (
"os"
"testing"
"time"
"github.com/evanw/esbuild/internal/helpers"
)
func TestHandleBuildRequestReleasesActiveBuildAfterError(t *testing.T) {
absWorkingDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
modifyRequest func(map[string]interface{})
expectedError string
expectedMessage string
}{
{
name: "InvalidPluginRegExp",
modifyRequest: func(request map[string]interface{}) {
request["plugins"] = []interface{}{map[string]interface{}{
"name": "test",
"onEnd": false,
"onResolve": []interface{}{map[string]interface{}{
"id": 0,
"filter": "x(?=y)",
"namespace": "",
}},
"onLoad": []interface{}{},
}}
},
expectedError: "[test] \"onResolve\" filter is not a valid Go regular expression: \"x(?=y)\"",
},
{
name: "InvalidContextOptions",
modifyRequest: func(request map[string]interface{}) {
request["context"] = true
request["flags"] = []interface{}{"--external:test"}
},
expectedMessage: "Cannot use \"external\" without \"bundle\"",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
service := serviceType{
activeBuilds: make(map[int]*activeBuild),
keepAliveWaitGroup: helpers.MakeThreadSafeWaitGroup(),
}
request := map[string]interface{}{
"context": false,
"key": 0,
"write": false,
"entries": []interface{}{},
"flags": []interface{}{},
"absWorkingDir": absWorkingDir,
"nodePaths": []interface{}{},
}
test.modifyRequest(request)
encoded := service.handleBuildRequest(0, request)
bytes, _, ok := readLengthPrefixedSlice(encoded)
if !ok {
t.Fatal("Invalid encoded response")
}
response, ok := decodePacket(bytes)
if !ok {
t.Fatal("Invalid response packet")
}
value := response.value.(map[string]interface{})
if test.expectedError != "" && value["error"] != test.expectedError {
t.Fatalf("Expected error %q, got %q", test.expectedError, value["error"])
}
if test.expectedMessage != "" {
errors := value["errors"].([]interface{})
if len(errors) != 1 || errors[0].(map[string]interface{})["text"] != test.expectedMessage {
t.Fatalf("Expected message %q, got %v", test.expectedMessage, errors)
}
}
if service.getActiveBuild(0) != nil {
t.Fatal("Active build was not released")
}
done := make(chan struct{})
go func() {
service.keepAliveWaitGroup.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("Keep-alive wait group did not reach zero")
}
})
}
}