github.com/wfusion/gofusion@v1.1.14/lock/candy.go (about)

     1  package lock
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"go.uber.org/multierr"
     8  
     9  	"github.com/wfusion/gofusion/common/utils"
    10  	"github.com/wfusion/gofusion/routine"
    11  )
    12  
    13  func Within(ctx context.Context, locker Lockable, key string,
    14  	expired, timeout time.Duration, cb func() error, opts ...utils.OptionExtender) (err error) {
    15  	const (
    16  		reLockWaitTime = time.Duration(200) * time.Millisecond
    17  	)
    18  	opt := utils.ApplyOptions[useOption](opts...)
    19  	optL := utils.ApplyOptions[lockOption](opts...)
    20  	if optL.reentrantKey == "" {
    21  		optL.reentrantKey = utils.ULID()
    22  	}
    23  	optionals := []utils.OptionExtender{ReentrantKey(optL.reentrantKey)}
    24  	if expired > 0 {
    25  		optionals = append(optionals, Expire(expired))
    26  	}
    27  
    28  	done := make(chan struct{}, 1)
    29  	timeFault := make(chan struct{}, 1)
    30  	routine.Goc(ctx, func() {
    31  		defer func() { done <- struct{}{} }()
    32  
    33  		var e error
    34  		rLocker, ok := locker.(ReentrantLockable)
    35  		for {
    36  			select {
    37  			case <-timeFault: // timeout exit
    38  				return
    39  			default:
    40  				if ok {
    41  					if e = rLocker.ReentrantLock(ctx, key, optL.reentrantKey, optionals...); e == nil {
    42  						return
    43  					}
    44  				} else {
    45  					if e = locker.Lock(ctx, key, optionals...); e == nil {
    46  						return
    47  					}
    48  				}
    49  
    50  				// relock after 200 milliseconds
    51  				time.Sleep(reLockWaitTime)
    52  			}
    53  		}
    54  	}, routine.AppName(opt.appName))
    55  
    56  	timer := time.NewTimer(timeout)
    57  	select {
    58  	// success
    59  	case <-done:
    60  
    61  	// context done
    62  	case <-ctx.Done():
    63  		timeFault <- struct{}{}
    64  		return ErrContextDone
    65  
    66  	// timeout
    67  	case <-timer.C:
    68  		timeFault <- struct{}{}
    69  		return ErrTimeout
    70  	}
    71  
    72  	defer func() { err = multierr.Append(err, locker.Unlock(ctx, key, optionals...)) }()
    73  
    74  	_, err = utils.Catch(cb)
    75  	return
    76  }