2025-10-03 14:32:45 +07:00

80 lines
1.8 KiB
Go

package enrollment
import (
"fmt"
"sync"
"time"
)
// IdempotencyStore stores results keyed by idempotency key.
var (
store map[string]string
storeMutex sync.RWMutex
)
// InitStore initializes the in-memory store.
func InitStore() {
store = make(map[string]string)
}
// EnrollCourseIdempotent processes an enrollment with idempotency key.
// If key exists, returns existing result immediately. If not, simulates
// 1.5s work, stores and returns result.
func EnrollCourseIdempotent(key string) string {
// check read lock first
storeMutex.RLock()
if res, ok := store[key]; ok {
storeMutex.RUnlock()
return res
}
storeMutex.RUnlock()
// Acquire write lock to ensure only one goroutine does the processing
storeMutex.Lock()
// double-check after acquiring write lock
if res, ok := store[key]; ok {
storeMutex.Unlock()
return res
}
// Simulate processing (1.5s)
time.Sleep(1500 * time.Millisecond)
res := fmt.Sprintf("enrolled-successful-for-key-%s", key)
// Save and release lock
store[key] = res
storeMutex.Unlock()
return res
}
// RunConcurrentEnrollments runs n goroutines that call EnrollCourseIdempotent with same key.
// Returns map: which goroutine got what result and runtime info.
func RunConcurrentEnrollments(key string, n int) map[string]interface{} {
var wg sync.WaitGroup
wg.Add(n)
resCh := make(chan string, n)
start := time.Now()
for i := 0; i < n; i++ {
go func(id int) {
defer wg.Done()
r := EnrollCourseIdempotent(key)
resCh <- fmt.Sprintf("goroutine-%d: %s", id+1, r)
}(i)
}
wg.Wait()
close(resCh)
out := make([]string, 0, n)
for r := range resCh {
out = append(out, r)
}
duration := time.Since(start)
return map[string]interface{}{
"key": key,
"count": n,
"duration": duration.String(),
"results": out,
}
}