[pipedv1] Data race in MetadataStoreRegistry causes fatal panic under concurrent deployments
Maintainer thường phản hồi trong vòng 2 ngày
@TonyWu2333 đang làm issue này rồi.
Từ ngày 5/10/2026.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 76/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Ít trao đổi
- Công nghệ
- go
- Lĩnh vực
- backend, distributed-systems, testing-qa
Hướng nghiên cứu
Bắt đầu với pkg/app/pipedv1/metadatastore/registry.go, sau đó kiểm tra cơ chế locking hiện có trong store.go và các test trong registry_test.go. Chạy go test -race ./pkg/app/pipedv1/metadatastore/... và sử dụng kịch bản truy cập đồng thời được cung cấp để xác minh rằng registry vẫn không có race và không panic trong khi các deployment được đăng ký, truy cập và xóa.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Description:
What happened:
MetadataStoreRegistry in pkg/app/pipedv1/metadatastore/registry.go holds a stores map[string]*metadataStore field with no synchronization primitive, causing a fatal concurrent map read and map write panic when multiple deployments run simultaneously.
The inner metadataStore correctly protects its own metadata maps with sync.RWMutex (store.go:47-49), but the outer registry map is unprotected. Two independent goroutine families access it without coordination:
- Writers —
Register(d)(registry.go:44) andDelete(d.Id)(registry.go:51) are called from planner/scheduler goroutines as deployments start and finish. - Readers — All 7 gRPC handler methods (
GetStageMetadata,PutStageMetadata,PutStageMetadataMulti,GetDeploymentPluginMetadata,PutDeploymentPluginMetadata,PutDeploymentPluginMetadataMulti,GetDeploymentSharedMetadata) readr.stores[req.DeploymentId](registry.go:47-119) from piped's plugin-service gRPC server goroutines.
When two or more applications deploy concurrently, a gRPC handler reading the map for deployment A races with Register/Delete for deployment B → Go runtime panics with fatal error: concurrent map read and map write → entire piped process aborts, killing all active deployments on that agent.
What you expected to happen:
MetadataStoreRegistry should safely serialize concurrent access to the stores map. Multiple deployments should coexist without data races or crashes.
How to reproduce it:
Add the following test to pkg/app/pipedv1/metadatastore/registry_test.go and run with the Go race detector:
package metadatastore
import (
"context"
"fmt"
"sync"
"testing"
"github.com/pipe-cd/pipecd/pkg/model"
service "github.com/pipe-cd/pipecd/pkg/plugin/pipedservice"
)
func TestRegistryConcurrentAccess(t *testing.T) {
t.Parallel()
ac := &fakeAPIClient{
shared: make(map[string]string),
plugins: make(map[string]metadata),
stages: make(map[string]metadata),
}
r := NewMetadataStoreRegistry(ac)
ctx := context.Background()
var wg sync.WaitGroup
const deployments = 20
const opsPerDeployment = 8
for i := 0; i < deployments; i++ {
// Writer: Register + Delete
wg.Add(1)
go func(idx int) {
defer wg.Done()
id := fmt.Sprintf("deploy-%d", idx)
d := &model.Deployment{
Id: id,
MetadataV2: &model.DeploymentMetadata{
Shared: &model.DeploymentMetadata_KeyValues{KeyValues: map[string]string{}},
Plugins: map[string]*model.DeploymentMetadata_KeyValues{},
},
Stages: []*model.PipelineStage{{Id: "stage-1"}},
}
r.Register(d)
r.Delete(id)
}(i)
// Readers: simulate gRPC handler calls
for j := 0; j < opsPerDeployment; j++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
id := fmt.Sprintf("deploy-%d", idx)
_, _ = r.PutStageMetadata(ctx, &service.PutStageMetadataRequest{
DeploymentId: id, StageId: "stage-1", Key: "k", Value: "v",
})
}(i)
}
}
wg.Wait()
}
Run command: go test -race ./pkg/app/pipedv1/metadatastore/...
Expected output (on unpatched master):
==================
WARNING: DATA RACE
Read at 0x... by goroutine ...:
(*MetadataStoreRegistry).PutStageMetadata
.../registry.go:59
Previous write at 0x... by goroutine ...:
(*MetadataStoreRegistry).Register
.../registry.go:44
==================
fatal error: concurrent map read and map write
Root Cause & Suggested Fix:
The MetadataStoreRegistry struct (registry.go:26-32) contains only apiClient and stores — no mutex. Register/Delete modify the map from lifecycle goroutines, while all 7 gRPC handlers read it from server goroutines, with no synchronization between them.
To fix, add a sync.RWMutex to the struct:
RLockin all 7 gRPC handlers for the map lookup (snapshot the pointer, release lock, then delegate to inner store).LockinRegisterandDeletefor map mutations.- Add a dedicated race test using channel-barrier synchronization for deterministic reproduction.
This pattern mirrors the existing locking convention one level down (metadataStore uses sync.RWMutex for sharedMu/pluginsMu/stagesMu) and introduces zero deadlock risk (registry lock is released before any inner store method or gRPC call).
Environment:
pipedversion:master(commit56fb80e)control-planeversion:master- Others: Go 1.26.2,
go test -race
- Ngôn ngữ chính
- Go
- Star
- 1.4k
- Fork
- 391
- Merge trung bình
- 4 ngày 1 giờ
- Pull request đã merge (30 ngày)
- 58
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của pipe-cd/pipecd
-
Applications at the repository root are not triggered by new commitsCó thể đã có người làm @Daksha1611 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 2 ngày
-
pipedv1 kubernetes plugins ignore trafficRouting.istio.host in K8S_TRAFFIC_ROUTINGCó thể đã có người làm @Dhirenderchoudhary đã nhận hôm nay. Đang mở
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 74/100
pipe-cd/pipecd#7626 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 2 ngày
-
pipectl transfer restore application drops backed-up labelsCó thể đã có người làm @bhavishyajain545 đã nhận 1 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
pipe-cd/pipecd#7622 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 83/100
Maintainer thường phản hồi trong vòng 2 ngày
Tất cả issue của pipe-cd/pipecd
Issue tương tự
-
IsTextFile reads the whole file into memory (OOM on large files in the file watcher)Có thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
gtsteffaniak/filebrowser#3395 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
effort:s impact:low raised-by:worker risk:low
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 70/100
medici-finance/assay#6496 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
kind/bug require/reproduce require/severity
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 60/100