github.com/vanus-labs/vanus/lib@v0.0.0-20231221070800-1334a7b9605e/sync/semaphore.go (about)

     1  // Copyright 2023 Linkall Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sync
    16  
    17  import (
    18  	_ "unsafe" // for go:linkname
    19  )
    20  
    21  type Semaphore struct {
    22  	sem     uint32
    23  	handoff bool
    24  }
    25  
    26  func (s *Semaphore) Init(handoff bool) *Semaphore {
    27  	s.handoff = handoff
    28  	return s
    29  }
    30  
    31  func (s *Semaphore) Acquire() {
    32  	semacquire(&s.sem)
    33  }
    34  
    35  func (s *Semaphore) Release() {
    36  	semrelease(&s.sem, s.handoff, 0)
    37  }
    38  
    39  //go:linkname semacquire sync.runtime_Semacquire
    40  func semacquire(addr *uint32)
    41  
    42  //go:linkname semrelease sync.runtime_Semrelease
    43  func semrelease(addr *uint32, handoff bool, skipframes int)
    44  
    45  /*
    46  import (
    47  	// standard libraries.
    48  	stdrt "runtime"
    49  	"sync/atomic"
    50  	"unsafe"
    51  
    52  	// third-party libraries.
    53  	"gvisor.dev/gvisor/pkg/sync"
    54  
    55  	// this project.
    56  	"github.com/vanus-labs/vanus/lib/container/conque/unbounded"
    57  	"github.com/vanus-labs/vanus/lib/runtime"
    58  )
    59  
    60  const (
    61  	running  uintptr = 0
    62  	runnable uintptr = 1
    63  
    64  	enableReloadPtr1 = false
    65  
    66  	activeSpin    = 4
    67  	activeSpinCnt = 30
    68  	passiveSpin   = 1
    69  )
    70  
    71  var ncpu = stdrt.NumCPU()
    72  
    73  type waiter struct {
    74  	// key is the status of waiter's goroutine. The value of key is running, runnable or gp (treats it as waiting).
    75  	key uintptr
    76  }
    77  
    78  type Semaphore struct {
    79  	sem int64
    80  	q   unbounded.Queue[*waiter]
    81  }
    82  
    83  func (s *Semaphore) Acquire() {
    84  	if atomic.AddInt64(&s.sem, -1) >= 0 {
    85  		return
    86  	}
    87  
    88  	w := &waiter{}
    89  	s.q.Push(w)
    90  
    91  	sync.Gopark(semaphoreCommit, unsafe.Pointer(w), sync.WaitReasonSemacquire, sync.TraceEvGoBlockSync, 0)
    92  }
    93  
    94  //go:norace
    95  func semaphoreCommit(gp uintptr, wp unsafe.Pointer) bool {
    96  	w := (*waiter)(wp)
    97  	// return atomic.CompareAndSwapUintptr(&w.key, running, gp)
    98  	return atomic.SwapUintptr(&w.key, gp) != runnable
    99  }
   100  
   101  func (s *Semaphore) Release() {
   102  	if atomic.AddInt64(&s.sem, 1) > 0 {
   103  		return
   104  	}
   105  
   106  	w := getWaiter(&s.q)
   107  
   108  	key := atomic.SwapUintptr(&w.key, runnable)
   109  	if key != running {
   110  		sync.Goready(key, 0, true)
   111  	}
   112  }
   113  
   114  func getWaiter(q *unbounded.Queue[*waiter]) *waiter {
   115  	if enableReloadPtr1 && ncpu <= 1 {
   116  		return getWaiter1(q)
   117  	}
   118  	return getWaiterN(q)
   119  }
   120  
   121  func getWaiter1(q *unbounded.Queue[*waiter]) *waiter {
   122  	for i := 0; ; i++ {
   123  		w, ok := q.SharedPop()
   124  		if ok {
   125  			return w
   126  		}
   127  
   128  		switch {
   129  		case i < passiveSpin:
   130  			runtime.OSYield()
   131  		default:
   132  			// TODO(james.yin): use condition variable?
   133  			runtime.OSYield()
   134  		}
   135  	}
   136  }
   137  
   138  func getWaiterN(q *unbounded.Queue[*waiter]) *waiter {
   139  	for i := 0; ; i++ {
   140  		w, ok := q.SharedPop()
   141  		if ok {
   142  			return w
   143  		}
   144  
   145  		switch {
   146  		case i < activeSpin:
   147  			runtime.ProcYield(activeSpinCnt)
   148  		case i < activeSpin+passiveSpin:
   149  			runtime.OSYield()
   150  		default:
   151  			// TODO(james.yin): use condition variable?
   152  			runtime.OSYield()
   153  		}
   154  	}
   155  }
   156  */