Skip to content

Instantly share code, notes, and snippets.

@rkuzner
Last active July 26, 2021 14:58
Show Gist options
  • Save rkuzner/d6ac5b7412afb4d5cdae2c0fc30adce6 to your computer and use it in GitHub Desktop.
Save rkuzner/d6ac5b7412afb4d5cdae2c0fc30adce6 to your computer and use it in GitHub Desktop.
Workshop Resiliencia - Demo Concurrencia
package racecondition_test
import (
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
)
// valores que usaremos en todos los tests
const (
Limite = 100 // valor maximo que queremos alcanzar con el contador
CargaDeTrabajo = 1 * time.Nanosecond // representa una carga de trabajo adicional previo a incrementar el contador
)
// HighLowBlock almacena los limites del bloque de numeros en uso para obtener consecutivos
// una vez que se usa el ultimo numero del bloque, debe renovarse por un nuevo bloque
type HighLowBlock struct {
Low uint64 // limite inferior del bloque de numeros en uso
High uint64 // limite superior del bloque de numeros en uso
}
var (
blockSize uint64 = 10 // tamaño del bloque
renewBlockMutex sync.Mutex
highLowBlockInUse HighLowBlock // bloque en uso por esta instancia
lastUsedNumber uint64 // ultimo numero usado por esta instancia
externalStorage = make(map[string]HighLowBlock) // representa el almacenamiento externo
)
// GetNextNumber devuelve el siguiente numero a ser utilizado
func getNextNumber() (uint64, error) {
var err error
var newLow uint64
var newHigh uint64
var nextNumber uint64
// queremos evitar race conditions mientras chequeamos/actualizamos el HighLowBlock
renewBlockMutex.Lock()
if lastUsedNumber == highLowBlockInUse.High {
// si alcanzamos el final del bloque, obtenemos un nuevo bloque desde el almacenamiento externo
if newLow, newHigh, err = getNextBlock(); err != nil {
renewBlockMutex.Unlock()
return 0, err
}
// pasamos los valores recibidos al HighLowBlock
highLowBlockInUse.Low = newLow
highLowBlockInUse.High = newHigh
// re-initializamos con el limite inferior del bloque de numeros
lastUsedNumber = newLow
}
renewBlockMutex.Unlock()
// usamos sync.atomic para actualizar el contador
nextNumber = atomic.AddUint64(&lastUsedNumber, 1)
return nextNumber, nil
}
// getNextBlock obtiene los nuevos limites desde un almacenamiento externo
func getNextBlock() (newLow uint64, newHigh uint64, err error) {
var externalMutex sync.Mutex
var highLowBlock HighLowBlock
var foundBlock bool
var blockKey = "hlb"
externalMutex.Lock() // representa la llamada al servicio externo de Mutex para obtener un Lock
defer externalMutex.Unlock() // representa la llamada al servicio externo de Mutex para liberar el Lock
fmt.Println()
// busco el bloque desde el almacenamiento externo
// (debería ser el ultimo bloque utilizado por alguna instancia)
if highLowBlock, foundBlock = externalStorage[blockKey]; !foundBlock {
// si no lo encontré entonces lo inicializo para poder guardarlo
highLowBlock = HighLowBlock{Low: 0, High: 0}
}
// aquí sucede la magia de la renovación del bloque
highLowBlock.Low = highLowBlock.High
highLowBlock.High = highLowBlock.Low + blockSize
// guardamos el nuevo bloque en almacenamiento externo
externalStorage[blockKey] = highLowBlock
fmt.Print(fmt.Sprintf("getNextBlock: %+v", highLowBlock))
return highLowBlock.Low, highLowBlock.High, nil
}
func TestGetNextNumber(t *testing.T) {
lastUsedNumber = 0
for i := 0; i < Limite; i++ {
time.Sleep(CargaDeTrabajo)
next, _ := getNextNumber()
fmt.Printf(" %d", next)
}
fmt.Println()
if lastUsedNumber != Limite {
t.Errorf("el contador NO alcanzó el límite: %d", lastUsedNumber)
} else {
fmt.Println(fmt.Sprintf("%s: el contador alcanzo el límite! (%+v)", t.Name(), highLowBlockInUse))
}
}
package racecondition_test
import (
"fmt"
"sync"
"testing"
"time"
)
const (
Workers = 3 // representa la cantidad de solicitudes concurrentes que obtienen números
)
func TestGetNextNumberConcurrenteConWaitGroup(t *testing.T) {
var wg sync.WaitGroup
lastUsedNumber = 0
for j := 0; j < Workers; j++ {
wg.Add(1)
go func(workerName int) {
defer wg.Done()
for i := 0; i < Limite; i++ {
time.Sleep(CargaDeTrabajo)
next, _ := getNextNumber()
fmt.Printf(" %d:%d", workerName, next)
}
}(j)
}
wg.Wait()
fmt.Println()
if lastUsedNumber != Limite*Workers {
t.Errorf("el contador NO alcanzó el límite: %d", lastUsedNumber)
} else {
fmt.Println(fmt.Sprintf("%s: el contador alcanzo el límite!", t.Name()))
}
if lastUsedNumber != highLowBlockInUse.High {
t.Errorf("el contador difiere del bloque: %+v <> %d", highLowBlockInUse, lastUsedNumber)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment