Skip to content

Commit

Permalink
Add generics support (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
brunomvsouza committed Oct 12, 2023
1 parent da8b0cc commit 8cf138c
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 32 deletions.
2 changes: 2 additions & 0 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Copyright (c) 2023 Bruno Marques Venceslau de Souza. All rights reserved.

Copyright (c) 2009 The Go Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Singleflight with Generics!

[![GoDoc](https://godoc.org/github.com/brunomvsouza/singleflight?status.svg)](https://godoc.org/github.com/brunomvsouza/singleflight)

> Package singleflight provides a duplicate function call suppression mechanism.
This fork adds generics support (`Group[K comparable, V any]` and `Result[V any]`) to the original [x/sync/singleflight](https://golang.org/x/sync/singleflight) package.

### Updates

- I will keep this package up-to-date with the original one, at least until `x/sync/singleflight` adds support for generics. If you notice an update before I do, please open an issue or submit a pull request.
- Versions will be tagged to align with the same versioning as the `x/sync/singleflight` package.

### Usage

For example usage, see:
- [Group.Do](examples/Do/main.go)
- [Group.DoChan](examples/DoChan/main.go)
- [Group.DoForget](examples/Forget/main.go)
54 changes: 54 additions & 0 deletions examples/Do/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2023 Bruno Marques Venceslau de Souza. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"fmt"
"sync"
"time"

"github.com/brunomvsouza/singleflight"
)

func main() {
var (
group singleflight.Group[string, string]
wg sync.WaitGroup
)

wg.Add(2)

go func() {
defer wg.Done()
res1, err1, shared1 := group.Do("key", func() (string, error) {
time.Sleep(10 * time.Millisecond)
return "func 1", nil
})
fmt.Println("res1:", res1)
fmt.Println("err1:", err1)
fmt.Println("shared1:", shared1)
}()

go func() {
defer wg.Done()
res2, err2, shared2 := group.Do("key", func() (string, error) {
time.Sleep(10 * time.Millisecond)
return "func 2", nil
})
fmt.Println("res2:", res2)
fmt.Println("err2:", err2)
fmt.Println("shared2:", shared2)
}()

wg.Wait()

// Output:
// res1: func 2
// err1: <nil>
// res2: func 2
// err2: <nil>
// shared2: true
// shared1: true
}
54 changes: 54 additions & 0 deletions examples/DoChan/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2023 Bruno Marques Venceslau de Souza. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"fmt"

"github.com/brunomvsouza/singleflight"
)

func main() {
var group singleflight.Group[string, string]

semaphore := make(chan struct{})

res1c := group.DoChan("key", func() (string, error) {
fmt.Printf("func 1 begin\n")
defer fmt.Printf("func 1 end\n")
<-semaphore
return "func 1", nil
})

res2c := group.DoChan("key", func() (string, error) {
fmt.Printf("func 2 begin\n")
defer fmt.Printf("func 2 end\n")
<-semaphore
return "func 2", nil
})

close(semaphore)

res1 := <-res1c
res2 := <-res2c

// Results are shared by functions executed with
// duplicate keys.
fmt.Println("Shared:", res2.Shared)
// Only the first function is executed: it is registered and
// started with "key", and doesn't complete before the second
// funtion is registered with a duplicate key.
fmt.Println("Equal results:", res1.Val == res2.Val)
fmt.Println("Result:", res1.Val)

// Output:
// Shared: true
// Equal results: true
// Result: func 1
}
75 changes: 75 additions & 0 deletions examples/Forget/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2023 Bruno Marques Venceslau de Souza. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"fmt"
"sync"
"time"

"github.com/brunomvsouza/singleflight"
)

func main() {
var (
group singleflight.Group[string, string]
wg sync.WaitGroup
)

wg.Add(4)

go func() {
defer wg.Done()
res1, err1, shared1 := group.Do("key", func() (string, error) {
time.Sleep(1 * time.Second)
return "func 1", nil
})
fmt.Println("res1:", res1)
fmt.Println("err1:", err1)
fmt.Println("shared1:", shared1)
}()

go func() {
defer wg.Done()
res2, err2, shared2 := group.Do("key", func() (string, error) {
time.Sleep(1 * time.Second)
return "func 2", nil
})
fmt.Println("res2:", res2)
fmt.Println("err2:", err2)
fmt.Println("shared2:", shared2)
}()

go func() {
defer wg.Done()
time.Sleep(100 * time.Millisecond)
group.Forget("key")
}()

go func() {
defer wg.Done()
time.Sleep(200 * time.Millisecond)
res3, err3, shared3 := group.Do("key", func() (string, error) {
time.Sleep(1 * time.Second)
return "func 3", nil
})
fmt.Println("res3:", res3)
fmt.Println("err3:", err3)
fmt.Println("shared3:", shared3)
}()

wg.Wait()

// Output:
// res2: func 1
// err2: <nil>
// shared2: true
// res1: func 1
// err1: <nil>
// shared1: true
// res3: func 3
// err3: <nil>
// shared3: false
}
44 changes: 26 additions & 18 deletions singleflight.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
// Copyright (c) 2023 Bruno Marques Venceslau de Souza. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package singleflight provides a duplicate function call suppression
// mechanism.
//
// This is a fork of Go's golang.org/x/sync/singleflight package, with the following
// changes:
// - Adds support for generics with Group[K comparable, V any] and Result[V any].
package singleflight

import (
Expand Down Expand Up @@ -53,32 +61,32 @@ func newPanicError(v interface{}) error {
}

// call is an in-flight or completed singleflight.Do call
type call struct {
type call[V any] struct {
wg sync.WaitGroup

// These fields are written once before the WaitGroup is done
// and are only read after the WaitGroup is done.
val interface{}
val V
err error

// These fields are read and written with the singleflight
// mutex held before the WaitGroup is done, and are read but
// not written after the WaitGroup is done.
dups int
chans []chan<- Result
chans []chan<- Result[V]
}

// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
type Group[K comparable, V any] struct {
mu sync.Mutex // protects m
m map[K]*call[V] // lazily initialized
}

// Result holds the results of Do, so they can be passed
// on a channel.
type Result struct {
Val interface{}
type Result[V any] struct {
Val V
Err error
Shared bool
}
Expand All @@ -88,10 +96,10 @@ type Result struct {
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
func (g *Group[K, V]) Do(key K, fn func() (V, error)) (v V, err error, shared bool) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
g.m = make(map[K]*call[V])
}
if c, ok := g.m[key]; ok {
c.dups++
Expand All @@ -105,7 +113,7 @@ func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, e
}
return c.val, c.err, true
}
c := new(call)
c := new(call[V])
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
Expand All @@ -118,19 +126,19 @@ func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, e
// results when they are ready.
//
// The returned channel will not be closed.
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
ch := make(chan Result, 1)
func (g *Group[K, V]) DoChan(key K, fn func() (V, error)) <-chan Result[V] {
ch := make(chan Result[V], 1)
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
g.m = make(map[K]*call[V])
}
if c, ok := g.m[key]; ok {
c.dups++
c.chans = append(c.chans, ch)
g.mu.Unlock()
return ch
}
c := &call{chans: []chan<- Result{ch}}
c := &call[V]{chans: []chan<- Result[V]{ch}}
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
Expand All @@ -141,7 +149,7 @@ func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result
}

// doCall handles the single call for a key.
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
func (g *Group[K, V]) doCall(c *call[V], key K, fn func() (V, error)) {
normalReturn := false
recovered := false

Expand Down Expand Up @@ -174,7 +182,7 @@ func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
} else {
// Normal return
for _, ch := range c.chans {
ch <- Result{c.val, c.err, c.dups > 0}
ch <- Result[V]{c.val, c.err, c.dups > 0}
}
}
}()
Expand Down Expand Up @@ -207,7 +215,7 @@ func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
// Forget tells the singleflight to forget about a key. Future calls
// to Do for this key will call the function rather than waiting for
// an earlier call to complete.
func (g *Group) Forget(key string) {
func (g *Group[K, V]) Forget(key K) {
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
Expand Down
Loading

0 comments on commit 8cf138c

Please sign in to comment.