github.com/searKing/golang/go@v1.2.117/sync/thread.go (about) 1 // Copyright 2021 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package sync 6 7 import ( 8 "context" 9 "errors" 10 "expvar" 11 "runtime" 12 "sync" 13 14 expvar_ "github.com/searKing/golang/go/expvar" 15 ) 16 17 var threadStatsOnce sync.Once 18 var threadStats *expvar.Map 19 var osThreadLeak, goroutineLeak, handlerLeak expvar_.Leak 20 21 //go:generate go-option -type=threadDo 22 type threadDo struct { 23 // call the function f in the same thread or escape thread 24 EscapeThread bool 25 } 26 27 // Thread should be used for such as calling OS services or 28 // non-Go library functions that depend on per-thread state, as runtime.LockOSThread(). 29 type Thread struct { 30 GoRoutine bool // Use thread as goroutine, that is without runtime.LockOSThread() 31 32 // The Leak is published as a variable directly. 33 GoroutineLeak *expvar_.Leak // represents whether goroutine is leaked, take effects if not nil 34 OSThreadLeak *expvar_.Leak // represents whether runtime.LockOSThread is leaked, take effects if not nil 35 HandlerLeak *expvar_.Leak // represents whether handler in Do is blocked is leaked, take effects if not nil 36 37 once sync.Once 38 // fCh optionally specifies a function to generate 39 // a value when Get would otherwise return nil. 40 // It may not be changed concurrently with calls to Get. 41 fCh chan func() 42 43 mu sync.Mutex 44 ctx context.Context 45 cancel context.CancelCauseFunc 46 } 47 48 // WatchStats bind Leak var to "sync.Thread" 49 func (t *Thread) WatchStats() { 50 threadStatsOnce.Do(func() { 51 threadStats = expvar.NewMap("sync.Thread") 52 threadStats.Set("goroutine_leak", &goroutineLeak) 53 threadStats.Set("os_thread_leak", &osThreadLeak) 54 threadStats.Set("handler_leak", &handlerLeak) 55 }) 56 t.GoroutineLeak = &goroutineLeak 57 t.OSThreadLeak = &osThreadLeak 58 t.HandlerLeak = &handlerLeak 59 } 60 61 // ErrThreadClosed is returned by the Thread's Do methods after a call to `Shutdown`. 62 var ErrThreadClosed = errors.New("sync: Thread closed") 63 64 func (t *Thread) Shutdown() { 65 t.initOnce() 66 t.cancel(ErrThreadClosed) 67 } 68 69 func (t *Thread) initOnce() { 70 t.once.Do(func() { 71 t.mu.Lock() 72 defer t.mu.Unlock() 73 74 t.ctx, t.cancel = context.WithCancelCause(context.Background()) 75 t.fCh = make(chan func()) 76 go t.lockOSThreadForever() 77 }) 78 } 79 80 // Do will call the function f in the same thread or escape thread. 81 // f is enqueued only if ctx is not canceled and Thread is not Shutdown and Not escape 82 func (t *Thread) Do(ctx context.Context, f func(), opts ...ThreadDoOption) error { 83 var opt threadDo 84 opt.ApplyOptions(opts...) 85 return t.do(ctx, f, opt.EscapeThread) 86 } 87 88 func (t *Thread) do(ctx context.Context, f func(), escapeThread bool) error { 89 t.initOnce() 90 if t.HandlerLeak != nil { 91 t.HandlerLeak.Add(1) 92 defer t.HandlerLeak.Done() 93 } 94 select { 95 case <-ctx.Done(): 96 return context.Cause(ctx) 97 case <-t.ctx.Done(): 98 return context.Cause(t.ctx) 99 default: 100 break 101 } 102 103 var r any 104 defer func() { 105 if r != nil { 106 panic(r) // rethrow panic if panic in f 107 } 108 }() 109 110 var wg sync.WaitGroup 111 wg.Add(1) 112 neverPanic := func() { 113 defer wg.Done() // Mark f is called or panic 114 defer func() { 115 r = recover() 116 }() 117 f() 118 } 119 120 if escapeThread { 121 neverPanic() 122 return nil 123 } 124 125 select { 126 case t.fCh <- neverPanic: 127 wg.Wait() // wait for f has been executed or panic 128 return nil 129 case <-ctx.Done(): 130 return context.Cause(ctx) 131 case <-t.ctx.Done(): 132 return context.Cause(t.ctx) 133 } 134 } 135 136 func (t *Thread) lockOSThreadForever() { 137 defer t.cancel(ErrThreadClosed) 138 if t.GoroutineLeak != nil { 139 t.GoroutineLeak.Add(1) 140 defer t.GoroutineLeak.Done() 141 } 142 if !t.GoRoutine { 143 runtime.LockOSThread() 144 defer runtime.UnlockOSThread() 145 if t.OSThreadLeak != nil { 146 t.OSThreadLeak.Add(1) 147 defer t.OSThreadLeak.Done() 148 } 149 } 150 for { 151 select { 152 case handler, ok := <-t.fCh: 153 if !ok { 154 return 155 } 156 if handler == nil { 157 continue 158 } 159 handler() 160 case <-t.ctx.Done(): 161 return 162 } 163 } 164 }