Refactor JSON marshaling in Options to improve thread safety

This commit is contained in:
2025-11-14 21:50:58 +01:00
parent 4f4feacaa8
commit 7544fbb1ce
2 changed files with 25 additions and 15 deletions

View File

@@ -79,14 +79,8 @@ func (o *Options) UnmarshalJSON(data []byte) error {
} }
func (o *Options) MarshalJSON() ([]byte, error) { func (o *Options) MarshalJSON() ([]byte, error) {
type Alias Options
aux := &struct {
*Alias
}{
Alias: (*Alias)(o),
}
// Get backend and marshal it // Get backend and marshal it
var backendOptions map[string]any
backend := o.getBackend() backend := o.getBackend()
if backend != nil { if backend != nil {
optionsData, err := json.Marshal(backend) optionsData, err := json.Marshal(backend)
@@ -94,13 +88,19 @@ func (o *Options) MarshalJSON() ([]byte, error) {
return nil, fmt.Errorf("failed to marshal backend options: %w", err) return nil, fmt.Errorf("failed to marshal backend options: %w", err)
} }
// Create a new map to avoid concurrent map writes // Create a new map to avoid concurrent map writes
aux.BackendOptions = make(map[string]any) backendOptions = make(map[string]any)
if err := json.Unmarshal(optionsData, &aux.BackendOptions); err != nil { if err := json.Unmarshal(optionsData, &backendOptions); err != nil {
return nil, fmt.Errorf("failed to unmarshal backend options to map: %w", err) return nil, fmt.Errorf("failed to unmarshal backend options to map: %w", err)
} }
} }
return json.Marshal(aux) return json.Marshal(&struct {
BackendType BackendType `json:"backend_type"`
BackendOptions map[string]any `json:"backend_options,omitempty"`
}{
BackendType: o.BackendType,
BackendOptions: backendOptions,
})
} }
// setBackendOptions stores the backend in the appropriate typed field // setBackendOptions stores the backend in the appropriate typed field

View File

@@ -7,6 +7,7 @@ import (
"llamactl/pkg/config" "llamactl/pkg/config"
"llamactl/pkg/validation" "llamactl/pkg/validation"
"log" "log"
"maps"
"slices" "slices"
"sync" "sync"
) )
@@ -144,15 +145,25 @@ func (c *Options) UnmarshalJSON(data []byte) error {
// MarshalJSON implements custom JSON marshaling for Options // MarshalJSON implements custom JSON marshaling for Options
func (c *Options) MarshalJSON() ([]byte, error) { func (c *Options) MarshalJSON() ([]byte, error) {
// Use anonymous struct to avoid recursion
type Alias Options type Alias Options
aux := struct {
// Make a copy of the struct
temp := *c
// Copy environment map to avoid concurrent access issues
if temp.Environment != nil {
envCopy := make(map[string]string, len(temp.Environment))
maps.Copy(envCopy, temp.Environment)
temp.Environment = envCopy
}
aux := &struct {
Nodes []string `json:"nodes,omitempty"` // Output as JSON array Nodes []string `json:"nodes,omitempty"` // Output as JSON array
BackendType backends.BackendType `json:"backend_type"` BackendType backends.BackendType `json:"backend_type"`
BackendOptions map[string]any `json:"backend_options,omitempty"` BackendOptions map[string]any `json:"backend_options,omitempty"`
*Alias *Alias
}{ }{
Alias: (*Alias)(c), Alias: (*Alias)(&temp),
} }
// Convert nodes map to array (sorted for consistency) // Convert nodes map to array (sorted for consistency)
@@ -169,13 +180,12 @@ func (c *Options) MarshalJSON() ([]byte, error) {
aux.BackendType = c.BackendOptions.BackendType aux.BackendType = c.BackendOptions.BackendType
// Marshal the backends.Options struct to get the properly formatted backend options // Marshal the backends.Options struct to get the properly formatted backend options
// Marshal a pointer to trigger the pointer receiver MarshalJSON method
backendData, err := json.Marshal(&c.BackendOptions) backendData, err := json.Marshal(&c.BackendOptions)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to marshal backend options: %w", err) return nil, fmt.Errorf("failed to marshal backend options: %w", err)
} }
// Unmarshal into a temporary struct to extract the backend_options map // Unmarshal into a new temporary map to extract the backend_options
var tempBackend struct { var tempBackend struct {
BackendOptions map[string]any `json:"backend_options,omitempty"` BackendOptions map[string]any `json:"backend_options,omitempty"`
} }