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  }