Skip to content

Commit

Permalink
✨ feat: Add the ability to merge multiple contexts into a single context
Browse files Browse the repository at this point in the history
  • Loading branch information
sohaha committed Aug 17, 2024
1 parent 0bd5ee8 commit 9b6a9da
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 2 deletions.
2 changes: 0 additions & 2 deletions zjson/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"unicode/utf16"
"unicode/utf8"

"github.com/sohaha/zlsgo/zlog"
"github.com/sohaha/zlsgo/zreflect"
"github.com/sohaha/zlsgo/zstring"
"github.com/sohaha/zlsgo/ztime"
Expand Down Expand Up @@ -1987,7 +1986,6 @@ func assign(jsval *Res, val reflect.Value, fmap *fieldMaps) {
s := key.Kind() == reflect.String
if s {
kind := t.Elem().Kind()
zlog.Debug(kind)
switch kind {
case reflect.Interface:
val.Set(zreflect.ValueOf(jsval.Value()))
Expand Down
105 changes: 105 additions & 0 deletions zsync/content.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package zsync

import (
"context"
"sync"
"time"
)

// MergeContext merges multiple contexts into a single one
func MergeContext(ctxs ...context.Context) Context {
if len(ctxs) == 0 {
return context.Background()
}

mc := mergeContext{
ctxs: ctxs,
doneCh: make(chan struct{}),
doneIndex: -1,
}
go mc.monitor()

return &mc
}

type Context interface {
context.Context
}

type mergeContext struct {
ctxs []context.Context
doneCh chan struct{}
doneIndex int
err error
}

func (mc *mergeContext) Deadline() (time.Time, bool) {
dl := time.Time{}
for _, ctx := range mc.ctxs {
thisDL, ok := ctx.Deadline()
if ok {
if dl.IsZero() {
dl = thisDL
} else if thisDL.Before(dl) {
dl = thisDL
}
}
}
return dl, !dl.IsZero()
}

func (mc *mergeContext) Done() <-chan struct{} {
return mc.doneCh
}

func (mc *mergeContext) Err() error {
return mc.err
}

func (mc *mergeContext) Value(key any) any {
for _, ctx := range mc.ctxs {
if v := ctx.Value(key); v != nil {
return v
}
}
return nil
}

func (mc *mergeContext) monitor() {
winner := multiselect(mc.ctxs)

mc.doneIndex = winner
mc.err = mc.ctxs[winner].Err()

close(mc.doneCh)
}

func multiselect(ctxs []context.Context) int {
res := make(chan int)

count := len(ctxs)
if count == 1 {
<-ctxs[0].Done()
return 0
}

var wg sync.WaitGroup
wg.Add(count)

for i, ctx := range ctxs {
go func(i int, ctx context.Context) {
defer wg.Done()
<-ctx.Done()
if ctx.Err() != nil {
}
res <- i
}(i, ctx)
}

go func() {
wg.Wait()
close(res)
}()

return <-res
}
62 changes: 62 additions & 0 deletions zsync/content_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package zsync

import (
"context"
"reflect"
"testing"
"time"

"github.com/sohaha/zlsgo"
)

func TestMergeContext(t *testing.T) {
tt := zlsgo.NewTest(t)

{
ctx1 := context.Background()
ctx2 := context.WithValue(context.Background(), "key", "value2")
ctx3, cancel3 := context.WithCancel(context.Background())

ctx := MergeContext(ctx1, ctx2, ctx3)
tt.Equal(reflect.TypeOf(ctx), reflect.TypeOf(&mergeContext{}))
tt.Equal("value2", ctx.Value("key"))

now := time.Now()
go func() {
time.Sleep(time.Second / 5)
cancel3()
}()

<-ctx.Done()

tt.EqualTrue(time.Since(now).Seconds() > 0.2)
}

{
ctx1 := context.Background()
ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second/5)
defer cancel2()
ctx := MergeContext(ctx1, ctx2)
now := time.Now()
select {
case <-ctx.Done():
tt.EqualTrue(ctx.Err() != nil)
}
tt.EqualTrue(time.Since(now).Seconds() > 0.2)
}

{
ctx1 := context.Background()
ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second/5)
ctx3, cancel3 := context.WithTimeout(context.Background(), time.Second/10)
defer cancel2()
defer cancel3()
ctx := MergeContext(ctx1, ctx2, ctx3)
now := time.Now()
select {
case <-ctx.Done():
tt.EqualTrue(ctx.Err() != nil)
}
tt.EqualTrue(time.Since(now).Seconds() > 0.1)
}
}

0 comments on commit 9b6a9da

Please sign in to comment.