mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed?
<!-- Describe what has changed in this PR -->
Remove license header from every file. Because it is really hard to
follow in this PR here is the summary:
1. License header is removed from all `.go` and `.proto` files
:fireworks::fireworks:🎆.
2. `LICENSE` file in the root directory has only Temporal and Uber
copyrights.
3. 5 other `LICENSE` files added to the packages which have copyrights
different from Temporal and Uber: Datadog, Xargin, "Mat Ryer, Tyler
Bunnell and contributors".
4. `license_file` flag is removed from all code generation tools.
5. `copyright_file` flag is removed from `go:generate mockgen`
directive.
6. All copyright related targets are removed from `Makefile`.
7. Updated Temporal copyright year to 2025 everywhere.
## Why?
<!-- Tell your future self why have you made these changes -->
I double checked with legal department that it is not needed to have
license header in every file. One file per repo is enough. I put all
copyrights to the root `LICENSE` file and removed header from all other
files. Also updated tools and `Makefile`.
241 lines
6.5 KiB
Go
241 lines
6.5 KiB
Go
package matching
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
"go.temporal.io/api/serviceerror"
|
|
persistencespb "go.temporal.io/server/api/persistence/v1"
|
|
"go.temporal.io/server/common"
|
|
"go.temporal.io/server/common/backoff"
|
|
"go.temporal.io/server/common/log"
|
|
"go.temporal.io/server/common/log/tag"
|
|
"go.temporal.io/server/common/metrics"
|
|
"go.temporal.io/server/common/persistence"
|
|
)
|
|
|
|
type (
|
|
writeTaskRequest struct {
|
|
subqueue int // for priTaskWriter only
|
|
taskInfo *persistencespb.TaskInfo
|
|
responseCh chan<- error
|
|
}
|
|
|
|
taskIDBlock struct {
|
|
start int64
|
|
end int64
|
|
}
|
|
|
|
// taskWriter writes tasks sequentially to persistence
|
|
taskWriter struct {
|
|
backlogMgr *backlogManagerImpl
|
|
config *taskQueueConfig
|
|
db *taskQueueDB
|
|
logger log.Logger
|
|
appendCh chan *writeTaskRequest
|
|
taskIDBlock taskIDBlock
|
|
currentTaskIDBlock taskIDBlock // copy of taskIDBlock for safe concurrent access via getCurrentTaskIDBlock()
|
|
}
|
|
)
|
|
|
|
var (
|
|
// errShutdown indicates that the task queue is shutting down
|
|
errShutdown = &persistence.ConditionFailedError{Msg: "task queue shutting down"}
|
|
errNonContiguousBlocks = errors.New("previous block end is not equal to current block")
|
|
|
|
noTaskIDs = taskIDBlock{start: 1, end: 0}
|
|
)
|
|
|
|
func newTaskWriter(
|
|
backlogMgr *backlogManagerImpl,
|
|
) *taskWriter {
|
|
return &taskWriter{
|
|
backlogMgr: backlogMgr,
|
|
config: backlogMgr.config,
|
|
db: backlogMgr.db,
|
|
logger: backlogMgr.logger,
|
|
appendCh: make(chan *writeTaskRequest, backlogMgr.config.OutstandingTaskAppendsThreshold()),
|
|
taskIDBlock: noTaskIDs,
|
|
}
|
|
}
|
|
|
|
// Start taskWriter background goroutine.
|
|
func (w *taskWriter) Start() {
|
|
go w.taskWriterLoop()
|
|
}
|
|
|
|
func (w *taskWriter) initReadWriteState() error {
|
|
retryForever := backoff.NewExponentialRetryPolicy(1 * time.Second).
|
|
WithMaximumInterval(10 * time.Second).
|
|
WithExpirationInterval(backoff.NoInterval)
|
|
|
|
state, err := w.renewLeaseWithRetry(retryForever, common.IsPersistenceTransientError)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.taskIDBlock = rangeIDToTaskIDBlock(state.rangeID, w.config.RangeSize)
|
|
w.currentTaskIDBlock = w.taskIDBlock
|
|
w.backlogMgr.taskAckManager.setAckLevel(state.ackLevel)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *taskWriter) appendTask(
|
|
taskInfo *persistencespb.TaskInfo,
|
|
) error {
|
|
|
|
select {
|
|
case <-w.backlogMgr.tqCtx.Done():
|
|
return errShutdown
|
|
default:
|
|
// noop
|
|
}
|
|
|
|
startTime := time.Now().UTC()
|
|
ch := make(chan error)
|
|
req := &writeTaskRequest{
|
|
taskInfo: taskInfo,
|
|
responseCh: ch,
|
|
}
|
|
|
|
select {
|
|
case w.appendCh <- req:
|
|
select {
|
|
case err := <-ch:
|
|
metrics.TaskWriteLatencyPerTaskQueue.With(w.backlogMgr.metricsHandler).Record(time.Since(startTime))
|
|
return err
|
|
case <-w.backlogMgr.tqCtx.Done():
|
|
// if we are shutting down, this request will never make
|
|
// it to cassandra, just bail out and fail this request
|
|
return errShutdown
|
|
}
|
|
default: // channel is full, throttle
|
|
metrics.TaskWriteThrottlePerTaskQueueCounter.With(w.backlogMgr.metricsHandler).Record(1)
|
|
return &serviceerror.ResourceExhausted{
|
|
Cause: enumspb.RESOURCE_EXHAUSTED_CAUSE_SYSTEM_OVERLOADED,
|
|
Scope: enumspb.RESOURCE_EXHAUSTED_SCOPE_NAMESPACE,
|
|
Message: "Too many outstanding appends to the task queue",
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *taskWriter) allocTaskIDs(count int) ([]int64, error) {
|
|
result := make([]int64, count)
|
|
for i := 0; i < count; i++ {
|
|
if w.taskIDBlock.start > w.taskIDBlock.end {
|
|
// we ran out of current allocation block
|
|
newBlock, err := w.allocTaskIDBlock(w.taskIDBlock.end)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w.taskIDBlock = newBlock
|
|
}
|
|
result[i] = w.taskIDBlock.start
|
|
w.taskIDBlock.start++
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (w *taskWriter) appendTasks(
|
|
taskIDs []int64,
|
|
reqs []*writeTaskRequest,
|
|
) error {
|
|
_, err := w.db.CreateTasks(w.backlogMgr.tqCtx, taskIDs, reqs)
|
|
if err != nil {
|
|
w.backlogMgr.signalIfFatal(err)
|
|
w.logger.Error("Persistent store operation failure",
|
|
tag.StoreOperationCreateTask,
|
|
tag.Error(err),
|
|
tag.WorkflowTaskQueueName(w.backlogMgr.queueKey().PersistenceName()),
|
|
tag.WorkflowTaskQueueType(w.backlogMgr.queueKey().TaskType()))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *taskWriter) taskWriterLoop() {
|
|
err := w.initReadWriteState()
|
|
w.backlogMgr.SetInitializedError(err)
|
|
|
|
for {
|
|
atomic.StoreInt64(&w.currentTaskIDBlock.start, w.taskIDBlock.start)
|
|
atomic.StoreInt64(&w.currentTaskIDBlock.end, w.taskIDBlock.end)
|
|
|
|
select {
|
|
case request := <-w.appendCh:
|
|
// read a batch of requests from the channel
|
|
reqs := []*writeTaskRequest{request}
|
|
reqs = w.getWriteBatch(reqs)
|
|
batchSize := len(reqs)
|
|
|
|
taskIDs, err := w.allocTaskIDs(batchSize)
|
|
if err == nil {
|
|
err = w.appendTasks(taskIDs, reqs)
|
|
}
|
|
for _, req := range reqs {
|
|
req.responseCh <- err
|
|
}
|
|
|
|
case <-w.backlogMgr.tqCtx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *taskWriter) getWriteBatch(reqs []*writeTaskRequest) []*writeTaskRequest {
|
|
readLoop:
|
|
for i := 0; i < w.config.MaxTaskBatchSize(); i++ {
|
|
select {
|
|
case req := <-w.appendCh:
|
|
reqs = append(reqs, req)
|
|
default: // channel is empty, don't block
|
|
break readLoop
|
|
}
|
|
}
|
|
return reqs
|
|
}
|
|
|
|
func (w *taskWriter) renewLeaseWithRetry(
|
|
retryPolicy backoff.RetryPolicy,
|
|
retryErrors backoff.IsRetryable,
|
|
) (taskQueueState, error) {
|
|
var newState taskQueueState
|
|
op := func(ctx context.Context) (err error) {
|
|
newState, err = w.db.RenewLease(ctx)
|
|
return
|
|
}
|
|
metrics.LeaseRequestPerTaskQueueCounter.With(w.backlogMgr.metricsHandler).Record(1)
|
|
err := backoff.ThrottleRetryContext(w.backlogMgr.tqCtx, op, retryPolicy, retryErrors)
|
|
if err != nil {
|
|
metrics.LeaseFailurePerTaskQueueCounter.With(w.backlogMgr.metricsHandler).Record(1)
|
|
return newState, err
|
|
}
|
|
return newState, nil
|
|
}
|
|
|
|
func (w *taskWriter) allocTaskIDBlock(prevBlockEnd int64) (taskIDBlock, error) {
|
|
currBlock := rangeIDToTaskIDBlock(w.db.RangeID(), w.config.RangeSize)
|
|
if currBlock.end != prevBlockEnd {
|
|
return taskIDBlock{}, errNonContiguousBlocks
|
|
}
|
|
state, err := w.renewLeaseWithRetry(persistenceOperationRetryPolicy, common.IsPersistenceTransientError)
|
|
if err != nil {
|
|
if w.backlogMgr.signalIfFatal(err) {
|
|
return taskIDBlock{}, errShutdown
|
|
}
|
|
return taskIDBlock{}, err
|
|
}
|
|
return rangeIDToTaskIDBlock(state.rangeID, w.config.RangeSize), nil
|
|
}
|
|
|
|
// getCurrentTaskIDBlock returns the current taskIDBlock. Safe to be called concurrently.
|
|
func (w *taskWriter) getCurrentTaskIDBlock() taskIDBlock {
|
|
return taskIDBlock{
|
|
start: atomic.LoadInt64(&w.currentTaskIDBlock.start),
|
|
end: atomic.LoadInt64(&w.currentTaskIDBlock.end),
|
|
}
|
|
}
|