diff --git a/internal/server/operations/operations.go b/internal/server/operations/operations.go index e83ccd1e4..d57c3f6da 100644 --- a/internal/server/operations/operations.go +++ b/internal/server/operations/operations.go @@ -153,6 +153,7 @@ func OperationCreate(s *state.State, projectName string, opClass OperationClass, op.SetEventServer(s.Events) } + // Validate and make a copy of the metadata to avoid concurrent reads/writes. newMetadata, err := parseMetadata(opMetadata) if err != nil { return nil, err @@ -506,7 +507,7 @@ func (op *Operation) Render() (string, *api.Operation, error) { op.lock.Lock() - // Make a copy of the metadata to avoid concurrent reads/writes. + // Make a read-only copy of the metadata to avoid concurrent reads/writes. metadata := map[string]any{} for k, v := range op.metadata { metadata[k] = v @@ -592,6 +593,7 @@ func (op *Operation) UpdateMetadata(opMetadata any) error { return errors.New("Read-only operations can't be updated") } + // Validate and make a copy of the metadata to avoid concurrent reads/writes. newMetadata, err := parseMetadata(opMetadata) if err != nil { return err @@ -635,7 +637,6 @@ func (op *Operation) ExtendMetadata(metadata any) error { // Get current metadata. newMetadata := op.metadata - op.lock.Unlock() // Merge with current one. if op.metadata == nil { @@ -645,7 +646,6 @@ func (op *Operation) ExtendMetadata(metadata any) error { } // Update the operation. - op.lock.Lock() op.updatedAt = time.Now() op.metadata = newMetadata op.lock.Unlock() @@ -665,9 +665,18 @@ func (op *Operation) ID() string { return op.id } -// Metadata returns the operation Metadata. +// Metadata returns a read-only copy of the operation metadata. func (op *Operation) Metadata() map[string]any { - return op.metadata + op.lock.Lock() + defer op.lock.Unlock() + + // Make a read-only copy of the metadata to avoid concurrent reads/writes. + metadata := map[string]any{} + for k, v := range op.metadata { + metadata[k] = v + } + + return metadata } // URL returns the operation URL.