@@ -170,53 +170,111 @@ func (g *group) Set(ctx context.Context, key string, value []byte, expire time.T
170
170
return errors .New ("empty Set() key not allowed" )
171
171
}
172
172
173
+ // Si no hay tamaño de caché, no hacemos nada
173
174
if g .maxCacheBytes <= 0 {
174
175
return nil
175
176
}
176
177
178
+ // Usamos singleflight para asegurar que solo una operación Set para la misma clave
179
+ // esté activa a la vez en este nodo (para la lógica de coordinación y actualización local/remota).
177
180
_ , err := g .setGroup .Do (key , func () (interface {}, error ) {
178
- // If remote peer owns this key
181
+
182
+ // *** INICIO: Crear una copia del valor ***
183
+ // Se crea una copia explícita del slice de bytes.
184
+ // Esto es crucial para evitar que las goroutines que actualizan los peers
185
+ // retengan referencias al slice 'value' original o a versiones antiguas,
186
+ // permitiendo que el GC libere la memoria de ciclos anteriores.
187
+ valueCopy := make ([]byte , len (value ))
188
+ copy (valueCopy , value )
189
+ // *** FIN: Crear una copia del valor ***
190
+
191
+ // Determinar el nodo "dueño" de la clave según el hash consistente.
179
192
owner , isRemote := g .instance .PickPeer (key )
193
+
194
+ // Si el dueño es un nodo remoto...
180
195
if isRemote {
181
- // Set the key/value on the remote peer
182
- if err := g .setPeer (ctx , owner , key , value , expire ); err != nil {
196
+ // ...enviar la operación Set al dueño (usando la copia).
197
+ // Esta llamada es síncrona dentro de la función de singleflight.Do.
198
+ if err := g .setPeer (ctx , owner , key , valueCopy , expire ); err != nil { // <-- Usa valueCopy
199
+ // Si falla la comunicación con el dueño, retornamos error.
200
+ // La política aquí podría variar (¿continuar actualizando otros peers?).
201
+ // Actualmente, si falla con el dueño, la operación Set completa falla.
183
202
return nil , err
184
203
}
185
204
}
186
- // Update the local caches
187
- bv := transport .ByteViewWithExpire (value , expire )
205
+
206
+ // Actualizar las cachés locales (mainCache y hotCache) de este nodo.
207
+ // Se usa la copia del valor para crear la ByteView.
208
+ bv := transport .ByteViewWithExpire (valueCopy , expire ) // <-- Usa valueCopy
209
+
210
+ // Usamos el lock del loadGroup (singleflight de Get) para sincronizar
211
+ // el acceso a las cachés locales (mainCache y hotCache).
212
+ // Esto previene condiciones de carrera si un Get local ocurre
213
+ // exactamente al mismo tiempo que esta actualización.
188
214
g .loadGroup .Lock (func () {
189
- g .mainCache .Add (key , bv )
190
- g .hotCache .Remove (key )
215
+ g .mainCache .Add (key , bv ) // Añade/sobrescribe en mainCache.
216
+ g .hotCache .Remove (key ) // Elimina de hotCache (si existía).
191
217
})
192
218
193
- // Update all peers in the cluster
219
+ // Actualizar todos los demás peers en el clúster (excepto este nodo y el dueño).
194
220
var wg sync.WaitGroup
195
221
for _ , p := range g .instance .getAllPeers () {
222
+ // Saltar la actualización a sí mismo.
196
223
if p .PeerInfo ().IsSelf {
197
- continue // Skip self
224
+ continue
198
225
}
199
226
200
- // Do not update the owner again, we already updated them
227
+ // Saltar la actualización al dueño (ya se hizo si era remoto,
228
+ // y si era local, la actualización local ya ocurrió).
201
229
if p .HashKey () == owner .HashKey () {
202
230
continue
203
231
}
204
232
233
+ // Incrementar el contador del WaitGroup para esta goroutine.
205
234
wg .Add (1 )
235
+ // Lanzar una goroutine para actualizar a este peer de forma asíncrona.
206
236
go func (p peer.Client ) {
207
- if err := g .setPeer (ctx , p , key , value , expire ); err != nil {
237
+ // *** INICIO: Asegurar wg.Done() y Recuperar Pánico ***
238
+ // defer wg.Done() es crucial para asegurar que Wait() no se bloquee
239
+ // indefinidamente si g.setPeer panica.
240
+ defer wg .Done ()
241
+
242
+ // Opcional pero recomendado: Recuperar pánicos dentro de la goroutine
243
+ // para loguearlos y evitar que el programa entero caiga.
244
+ defer func () {
245
+ if r := recover (); r != nil {
246
+ g .instance .opts .Logger .Error ("PANIC during setPeer" ,
247
+ "peer" , p .PeerInfo ().Address ,
248
+ "key" , key ,
249
+ "panic" , fmt .Sprintf ("%v" , r ),
250
+ // Considerar loguear stack trace: "stack", string(debug.Stack()),
251
+ )
252
+ }
253
+ }()
254
+ // *** FIN: Asegurar wg.Done() y Recuperar Pánico ***
255
+
256
+ // Llamar a setPeer para enviar la operación Set al peer (usando la copia).
257
+ // La goroutine captura 'valueCopy', que es la copia específica de esta ejecución de Set.
258
+ if err := g .setPeer (ctx , p , key , valueCopy , expire ); err != nil { // <-- Usa valueCopy
259
+ // Loguear errores de comunicación con el peer, pero no hacer fallar
260
+ // la operación Set principal (es un esfuerzo "best-effort").
208
261
g .instance .opts .Logger .Error ("while calling Set on peer" ,
209
262
"peer" , p .PeerInfo ().Address ,
210
263
"key" , key ,
211
264
"err" , err )
212
265
}
213
- wg .Done ()
214
- }(p )
266
+ }(p ) // Pasar el peer 'p' como argumento a la goroutine
215
267
}
268
+ // Esperar a que todas las goroutines de actualización de peers terminen.
269
+ // Esto asegura que la llamada a group.Set no retorne hasta que se haya
270
+ // intentado actualizar a todos los peers.
216
271
wg .Wait ()
217
272
273
+ // La función de singleflight.Do retorna nil en caso de éxito.
218
274
return nil , nil
219
- })
275
+ }) // Fin de singleflight.Do
276
+
277
+ // Retornar el error de singleflight.Do (si lo hubo).
220
278
return err
221
279
}
222
280
@@ -235,38 +293,93 @@ func (g *group) Remove(ctx context.Context, key string) error {
235
293
owner , isRemote := g .instance .PickPeer (key )
236
294
if isRemote {
237
295
if err := g .removeFromPeer (ctx , owner , key ); err != nil {
296
+ // Si falla la eliminación en el dueño, retornamos error.
297
+ // Podría considerarse continuar con los otros peers, pero
298
+ // actualmente la operación Remove completa falla.
238
299
return nil , err
239
300
}
240
301
}
241
302
// Remove from our cache next
242
- g .LocalRemove (key )
303
+ g .LocalRemove (key ) // Elimina de mainCache y hotCache locales
304
+
305
+ // --- INICIO MODIFICACIÓN ---
243
306
wg := sync.WaitGroup {}
244
- errCh := make (chan error )
307
+ // Usamos un buffered channel para evitar bloqueos si hay muchos errores rápidos
308
+ // y el lector (más abajo) no es lo suficientemente rápido. El tamaño puede ajustarse.
309
+ numPeersToNotify := 0
310
+ for _ , p := range g .instance .getAllPeers () {
311
+ if p != owner { // Contar cuántos peers necesitan notificación
312
+ numPeersToNotify ++
313
+ }
314
+ }
315
+ // Crear canal con buffer suficiente para todos los posibles errores + 1 (por si acaso)
316
+ errCh := make (chan error , numPeersToNotify + 1 )
245
317
246
318
// Asynchronously clear the key from all hot and main caches of peers
247
319
for _ , p := range g .instance .getAllPeers () {
248
320
// avoid deleting from owner a second time
249
321
if p == owner {
250
322
continue
251
323
}
324
+ // Saltar a sí mismo (LocalRemove ya lo hizo)
325
+ if p .PeerInfo ().IsSelf {
326
+ continue
327
+ }
252
328
253
329
wg .Add (1 )
254
330
go func (p peer.Client ) {
255
- errCh <- g .removeFromPeer (ctx , p , key )
256
- wg .Done ()
257
- }(p )
331
+ // *** AÑADIR defer wg.Done() ***
332
+ // Asegura que wg.Done() se llame incluso si removeFromPeer panica.
333
+ defer wg .Done ()
334
+
335
+ // *** Opcional pero recomendado: Añadir recover() ***
336
+ defer func () {
337
+ if r := recover (); r != nil {
338
+ // Loguear el pánico
339
+ g .instance .opts .Logger .Error ("PANIC during removeFromPeer" ,
340
+ "peer" , p .PeerInfo ().Address ,
341
+ "key" , key ,
342
+ "panic" , fmt .Sprintf ("%v" , r ),
343
+ // Considerar loguear stack trace: "stack", string(debug.Stack()),
344
+ )
345
+ // Opcionalmente, enviar un error específico al canal
346
+ // errCh <- fmt.Errorf("panic during removeFromPeer for peer %s: %v", p.PeerInfo().Address, r)
347
+ }
348
+ }()
349
+
350
+ // Llamar a removeFromPeer y enviar el resultado (error o nil) al canal
351
+ err := g .removeFromPeer (ctx , p , key )
352
+ if err != nil {
353
+ // Loguear el error específico de este peer
354
+ g .instance .opts .Logger .Error ("while calling Remove on peer" ,
355
+ "peer" , p .PeerInfo ().Address ,
356
+ "key" , key ,
357
+ "err" , err )
358
+ }
359
+ // Enviar el error (puede ser nil) al canal para agregación
360
+ errCh <- err
361
+
362
+ // wg.Done() // Ya no es necesario aquí explícitamente
363
+ }(p ) // Pasar el peer 'p' como argumento
258
364
}
365
+
366
+ // Goroutine para esperar a que todas las llamadas a removeFromPeer terminen y luego cerrar el canal de errores
259
367
go func () {
260
368
wg .Wait ()
261
369
close (errCh )
262
370
}()
371
+ // --- FIN MODIFICACIÓN ---
263
372
264
- m := & MultiError {}
373
+ // Recolectar todos los errores del canal
374
+ m := & MultiError {} // Asumiendo que tienes una estructura MultiError o similar
265
375
for err := range errCh {
266
- m .Add (err )
376
+ if err != nil { // Solo añadir errores reales
377
+ m .Add (err )
378
+ }
267
379
}
268
380
269
- return nil , m .NilOrError ()
381
+ // Retornar nil si no hubo errores, o el MultiError si los hubo
382
+ return nil , m .NilOrError () // Asumiendo que NilOrError() devuelve nil si no hay errores
270
383
})
271
384
return err
272
385
}
0 commit comments