Skip to content

Commit 8134fd5

Browse files
committed
Rename/reword to semaphore
1 parent e9341ad commit 8134fd5

File tree

6 files changed

+79
-60
lines changed

6 files changed

+79
-60
lines changed

README.md

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
# Nice
1+
# Semaphore
22

3-
[![Go Reference](https://pkg.go.dev/badge/github.com/aertje/gonice.svg)](https://pkg.go.dev/github.com/aertje/gonice)
4-
[![Go Report Card](https://goreportcard.com/badge/github.com/aertje/gonice)](https://goreportcard.com/report/github.com/aertje/gonice)
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/aertje/semaphore.svg)](https://pkg.go.dev/github.com/aertje/semaphore)
4+
[![Go Report Card](https://goreportcard.com/badge/github.com/aertje/semaphore)](https://goreportcard.com/report/github.com/aertje/semaphore)
55
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
66

7-
The `nice` package provides a priority-based concurrency control mechanism. It allows you to manage the execution of functions based on their priority while respecting a maximum concurrency limit. This is particularly useful in scenarios where certain tasks need to be prioritised over others, and there is a need to limit the number of concurrent tasks to avoid overloading the system.
7+
The `semaphore` package provides a priority-based concurrency control mechanism. It allows you to manage the execution of functions based on their priority while respecting a maximum concurrency limit. This is particularly useful in scenarios where certain tasks need to be prioritised over others, and there is a need to limit the number of concurrent tasks to avoid overloading the system.
88

99
The general use case is to prioritise certain CPU-bound tasks over others. For example, in a web service, it could be used for example to prioritise the alive endpoint over the metrics endpoint, or to serve bulk requests before real-time requests.
1010

11-
The implementation does not interfere with Go's runtime scheduler. It is opt-in and does not affect the behavior of other goroutines in the application.
11+
The implementation does not interfere with Go's runtime semaphore. It is opt-in and does not affect the behavior of other goroutines in the application.
1212

1313
## Features
1414

@@ -21,12 +21,12 @@ The implementation does not interfere with Go's runtime scheduler. It is opt-in
2121
To install the package, use the following command:
2222

2323
```sh
24-
go get github.com/aertje/gonice
24+
go get github.com/aertje/semaphore
2525
```
2626

2727
## Simple example
2828

29-
The following minimal example demonstrates how to use the `nice` package to create a scheduler that starts tasks based on their priority. It illustrates the required steps to create a scheduler, register a task with a specific priority, and signal the completion of the task.
29+
The following minimal example demonstrates how to use the `semaphore` package to create a semaphore that starts tasks based on their priority. It illustrates the required steps to create a semaphore, register a task with a specific priority, and signal the completion of the task.
3030

3131
```go
3232
package main
@@ -35,17 +35,17 @@ import (
3535
"fmt"
3636
"time"
3737

38-
"github.com/aertje/gonice/nice"
38+
"github.com/aertje/semaphore/semaphore"
3939
)
4040

4141
func main() {
42-
// Create a new scheduler with the default maximum concurrency limit.
43-
scheduler := nice.NewScheduler()
42+
// Create a new prioritized semaphore with the default maximum concurrency limit.
43+
s := semaphore.NewPrioritized()
4444

45-
// Register a task with the scheduler with a priority of 1.
46-
fnDone := scheduler.Wait(1)
47-
// Signal the completion of the task.
48-
defer fnDone()
45+
// Register a task with the semaphore with a priority of 1.
46+
s.Acquire(1)
47+
// Ensure signalling the completion of the task.
48+
defer s.Release(1)
4949

5050
// Simulate a long-running task.
5151
time.Sleep(1 * time.Second)
@@ -54,23 +54,23 @@ func main() {
5454

5555
The steps are as follows:
5656

57-
- Create a new scheduler with an optional maximum concurrency limit.
57+
- Create a new semaphore with an optional maximum concurrency limit.
5858

5959
Then, for each task to be prioritised:
6060

61-
- Register a task with the scheduler using the `Wait` method. This will block until the task can be executed. It returns a function that should be called to signal the completion of the task.
61+
- Register a task with the semaphore using the `Acquire` method. This will block until the task can be executed.
6262
- Execute the task.
63-
- Call the function returned from the call to `Wait` to signal the completion of the task to the scheduler.
63+
- Call the `Release` method to signal the completion of the task to the semaphore.
6464

65-
Note the importance of calling the function returned by `Wait` to signal the completion of the task. This is necessary to allow other tasks to be executed by the scheduler.
65+
Note the importance of calling the `Release` method to signal the completion of the task. This is necessary to allow other tasks to be executed by the semaphore.
6666

67-
If the context needs to be taken into account in order to support cancellation, the `WaitContext` method can be used instead.
67+
If the context needs to be taken into account in order to support cancellation, the `AcquireContext` method can be used instead.
6868

6969
## Example use case: Prioritizing `/alive` endpoint
7070

71-
In this example, we will create a scheduler that prioritises an `/alive` endpoint over other endpoints. This is useful in scenarios where the `/alive` endpoint is critical and needs to be executed before other endpoints.
71+
In this example, we will create a semaphore that prioritises an `/alive` endpoint over other endpoints. This is useful in scenarios where the `/alive` endpoint is critical and needs to be executed before other endpoints.
7272

73-
It also demonstrates use of the `WaitContext` method to support context cancellation. This is useful in scenarios where the client cancels the request, and the server should dispose of the task.
73+
It also demonstrates use of the `AcquireContext` method to support context cancellation. This is useful in scenarios where the client cancels the request, and the server should dispose of the task.
7474

7575
```go
7676
package main
@@ -81,16 +81,16 @@ import (
8181
"net/http"
8282
"time"
8383

84-
"github.com/aertje/gonice/nice"
84+
"github.com/aertje/semaphore/semaphore"
8585
)
8686

8787
func main() {
88-
// Create a new scheduler with a maximum concurrency limit of 10.
89-
scheduler := nice.NewScheduler(nice.WithMaxConcurrency(10))
88+
// Create a new semaphore with a maximum concurrency limit of 10.
89+
s := semaphore.NewPrioritized(semaphore.WithMaxConcurrency(10))
9090

9191
http.HandleFunc("/alive", func(w http.ResponseWriter, r *http.Request) {
92-
// Register a task with the scheduler with a higher priority of 1.
93-
fnDone, err := scheduler.WaitContext(r.Context(), 1)
92+
// Register a task with the semaphore with a higher priority of 1.
93+
err := s.AcquireContext(r.Context(), 1)
9494
if err != nil {
9595
if errors.Is(err, context.Canceled) {
9696
http.Error(w, context.Cause(r.Context()).Error(), 499)
@@ -100,14 +100,14 @@ func main() {
100100
http.Error(w, err.Error(), http.StatusInternalServerError)
101101
return
102102
}
103-
defer fnDone()
103+
defer s.Release()
104104

105105
w.Write([]byte("I'm alive!"))
106106
})
107107

108108
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
109-
// Register a task with the scheduler with a lower priority of 2.
110-
fnDone, err := scheduler.WaitContext(r.Context(), 2)
109+
// Register a task with the semaphore with a lower priority of 2.
110+
err := s.AcquireContext(r.Context(), 2)
111111
if err != nil {
112112
if errors.Is(err, context.Canceled) {
113113
http.Error(w, context.Cause(r.Context()).Error(), 499)
@@ -118,7 +118,7 @@ func main() {
118118
return
119119
}
120120

121-
defer fnDone()
121+
defer s.Release()
122122

123123
time.Sleep(1 * time.Second)
124124

context/context.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package context
2+
3+
import (
4+
"context"
5+
6+
"github.com/aertje/semaphore/semaphore"
7+
)
8+
9+
type schedulerKey struct{}
10+
11+
var key = schedulerKey{}
12+
13+
func SchedulerFromContext(ctx context.Context) (*semaphore.Prioritized, bool) {
14+
s, ok := ctx.Value(key).(*semaphore.Prioritized)
15+
return s, ok
16+
}
17+
18+
func WithScheduler(ctx context.Context, s *semaphore.Prioritized) context.Context {
19+
return context.WithValue(ctx, key, s)
20+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/aertje/gonice
1+
module github.com/aertje/semaphore
22

33
go 1.23.0
44

queue/queue_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"container/heap"
55
"testing"
66

7-
"github.com/aertje/gonice/queue"
7+
"github.com/aertje/semaphore/queue"
88
"github.com/stretchr/testify/assert"
99
)
1010

nice/nice.go renamed to semaphore/semaphore.go

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
package nice
1+
package semaphore
22

33
import (
44
"container/heap"
55
"context"
66
"runtime"
77
"sync"
88

9-
"github.com/aertje/gonice/queue"
9+
"github.com/aertje/semaphore/queue"
1010
)
1111

1212
type entry struct {
13-
priority int
1413
waitChan chan<- struct{}
1514
cancelChan <-chan struct{}
1615
}
1716

18-
type Scheduler struct {
17+
type Prioritized struct {
1918
maxConcurrency int
2019

2120
concurrency int
@@ -24,16 +23,16 @@ type Scheduler struct {
2423
entries *queue.Q[entry]
2524
}
2625

27-
type Option func(*Scheduler)
26+
type Option func(*Prioritized)
2827

2928
func WithMaxConcurrency(maxConcurrency int) Option {
30-
return func(p *Scheduler) {
29+
return func(p *Prioritized) {
3130
p.maxConcurrency = maxConcurrency
3231
}
3332
}
3433

35-
func NewScheduler(opts ...Option) *Scheduler {
36-
s := &Scheduler{
34+
func NewPrioritized(opts ...Option) *Prioritized {
35+
s := &Prioritized{
3736
maxConcurrency: runtime.GOMAXPROCS(0),
3837
entries: new(queue.Q[entry]),
3938
}
@@ -47,7 +46,7 @@ func NewScheduler(opts ...Option) *Scheduler {
4746
return s
4847
}
4948

50-
func (s *Scheduler) assessEntries() {
49+
func (s *Prioritized) assessEntries() {
5150
s.lock.Lock()
5251
defer s.lock.Unlock()
5352

@@ -72,7 +71,7 @@ func (s *Scheduler) assessEntries() {
7271
}
7372
}
7473

75-
func (s *Scheduler) WaitContext(ctx context.Context, priority int) error {
74+
func (s *Prioritized) AcquireContext(ctx context.Context, priority int) error {
7675
waitChan := make(chan struct{})
7776
cancelChan := make(chan struct{})
7877

@@ -98,11 +97,11 @@ func (s *Scheduler) WaitContext(ctx context.Context, priority int) error {
9897
}
9998
}
10099

101-
func (s *Scheduler) Wait(priority int) {
102-
s.WaitContext(context.Background(), priority)
100+
func (s *Prioritized) Acquire(priority int) {
101+
s.AcquireContext(context.Background(), priority)
103102
}
104103

105-
func (s *Scheduler) Done() {
104+
func (s *Prioritized) Release() {
106105
s.lock.Lock()
107106
defer s.lock.Unlock()
108107
s.concurrency--
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
package nice_test
1+
package semaphore_test
22

33
import (
44
"context"
55
"sync"
66
"testing"
77
"time"
88

9-
"github.com/aertje/gonice/nice"
9+
"github.com/aertje/semaphore/semaphore"
1010
"github.com/stretchr/testify/assert"
1111
"github.com/stretchr/testify/require"
1212
)
1313

1414
func TestSimple(t *testing.T) {
15-
s := nice.NewScheduler()
15+
s := semaphore.NewPrioritized()
1616

17-
s.Wait(1)
18-
s.Done()
17+
s.Acquire(1)
18+
s.Release()
1919
}
2020

2121
func TestOrderConcurrency(t *testing.T) {
@@ -52,14 +52,14 @@ func TestOrderConcurrency(t *testing.T) {
5252
}
5353

5454
func testOrderForConcurrency(maxConcurrency int, totalTasks int) []int {
55-
s := nice.NewScheduler(nice.WithMaxConcurrency(maxConcurrency))
55+
s := semaphore.NewPrioritized(semaphore.WithMaxConcurrency(maxConcurrency))
5656

5757
// Saturate the scheduler otherwise subsequent tasks will be executed
5858
// immediately in undefined order.
5959
for i := 0; i < maxConcurrency; i++ {
6060
go func() {
61-
s.Wait(0)
62-
defer s.Done()
61+
s.Acquire(0)
62+
defer s.Release()
6363
time.Sleep(10 * time.Millisecond)
6464
}()
6565
}
@@ -78,8 +78,8 @@ func testOrderForConcurrency(maxConcurrency int, totalTasks int) []int {
7878
go func() {
7979
defer wg.Done()
8080

81-
s.Wait(priority)
82-
defer s.Done()
81+
s.Acquire(priority)
82+
defer s.Release()
8383

8484
time.Sleep(10 * time.Millisecond)
8585

@@ -96,14 +96,14 @@ func testOrderForConcurrency(maxConcurrency int, totalTasks int) []int {
9696
}
9797

9898
func TestCancel(t *testing.T) {
99-
s := nice.NewScheduler(nice.WithMaxConcurrency(1))
99+
s := semaphore.NewPrioritized(semaphore.WithMaxConcurrency(1))
100100

101101
// Saturate the scheduler otherwise the task under test will be executed
102102
// immediately without waiting.
103103
go func() {
104-
s.Wait(0)
104+
s.Acquire(0)
105105
time.Sleep(10 * time.Millisecond)
106-
s.Done()
106+
s.Release()
107107
}()
108108

109109
// Give the scheduler some time to start the goroutine.
@@ -112,8 +112,8 @@ func TestCancel(t *testing.T) {
112112
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond)
113113
defer cancel()
114114

115-
err := s.WaitContext(ctx, 1)
116-
defer s.Done()
115+
err := s.AcquireContext(ctx, 1)
116+
defer s.Release()
117117

118118
require.Error(t, err)
119119
assert.Equal(t, context.DeadlineExceeded, err)

0 commit comments

Comments
 (0)