From bc1e73925b813998db344b757bf48c0d50fe495b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:40:57 +0000 Subject: [PATCH 1/3] Initial plan From 49a5de4021574c2faa747de775c80a72583075b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:47:15 +0000 Subject: [PATCH 2/3] Improve performance with optimized slice operations Co-authored-by: Ren0503 <54244073+Ren0503@users.noreply.github.com> --- benchmark_test.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++ job.go | 2 - queue.go | 17 +++--- 3 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 benchmark_test.go diff --git a/benchmark_test.go b/benchmark_test.go new file mode 100644 index 0000000..d85f84d --- /dev/null +++ b/benchmark_test.go @@ -0,0 +1,134 @@ +package queue + +import ( + "testing" +) + +// BenchmarkMergeSortedJobs benchmarks the mergeSortedJobs function +func BenchmarkMergeSortedJobs(b *testing.B) { + // Create two sorted job slices + jobs1 := make([]Job, 100) + jobs2 := make([]Job, 100) + for i := 0; i < 100; i++ { + jobs1[i] = Job{Priority: 200 - i*2} + jobs2[i] = Job{Priority: 199 - i*2} + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = mergeSortedJobs(jobs1, jobs2) + } +} + +// BenchmarkSliceInsertion benchmarks the optimized slice insertion +func BenchmarkSliceInsertion(b *testing.B) { + b.Run("optimized_copy", func(b *testing.B) { + for i := 0; i < b.N; i++ { + jobs := make([]Job, 100) + for j := 0; j < 100; j++ { + jobs[j] = Job{Priority: 100 - j} + } + // Optimized insertion at middle + insertIdx := 50 + job := Job{Priority: 75} + jobs = append(jobs, Job{}) + copy(jobs[insertIdx+1:], jobs[insertIdx:]) + jobs[insertIdx] = job + } + }) + + b.Run("double_append", func(b *testing.B) { + for i := 0; i < b.N; i++ { + jobs := make([]Job, 100) + for j := 0; j < 100; j++ { + jobs[j] = Job{Priority: 100 - j} + } + // Old double-append pattern + insertIdx := 50 + job := Job{Priority: 75} + jobs = append(jobs[:insertIdx], append([]Job{job}, jobs[insertIdx:]...)...) + _ = jobs + } + }) +} + +// BenchmarkPreallocatedSlice benchmarks the preallocated vs non-preallocated slice +func BenchmarkPreallocatedSlice(b *testing.B) { + b.Run("preallocated", func(b *testing.B) { + for i := 0; i < b.N; i++ { + jobs := make([]*Job, 0, 100) + for j := 0; j < 100; j++ { + jobs = append(jobs, &Job{Priority: j}) + } + } + }) + + b.Run("non_preallocated", func(b *testing.B) { + for i := 0; i < b.N; i++ { + jobs := []*Job{} + for j := 0; j < 100; j++ { + jobs = append(jobs, &Job{Priority: j}) + } + } + }) +} + +// BenchmarkSliceClear benchmarks clearing a slice +func BenchmarkSliceClear(b *testing.B) { + b.Run("reuse_capacity", func(b *testing.B) { + for i := 0; i < b.N; i++ { + jobs := make([]*Job, 100) + for j := 0; j < 100; j++ { + jobs[j] = &Job{Priority: j} + } + // Clear by reslicing to reuse capacity + jobs = jobs[:0] + _ = jobs + } + }) + + b.Run("new_slice", func(b *testing.B) { + for i := 0; i < b.N; i++ { + jobs := make([]*Job, 100) + for j := 0; j < 100; j++ { + jobs[j] = &Job{Priority: j} + } + // Create new empty slice + jobs = []*Job{} + _ = jobs + } + }) +} + +// BenchmarkMin benchmarks the Min function +func BenchmarkMin(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = Min(100, 50) + _ = Min(50, 100) + } +} + +// BenchmarkJobCreation benchmarks job creation with nil vs empty stacktrace +func BenchmarkJobCreation(b *testing.B) { + b.Run("nil_stacktrace", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = &Job{ + Id: "test", + Priority: 1, + Status: WaitStatus, + } + } + }) + + b.Run("empty_stacktrace", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = &Job{ + Id: "test", + Priority: 1, + Status: WaitStatus, + Stacktrace: []string{}, + } + } + }) +} + diff --git a/job.go b/job.go index 42aa051..2fb6ae4 100644 --- a/job.go +++ b/job.go @@ -37,7 +37,6 @@ func (queue *Queue) newJob(opt AddJobOptions) *Job { Data: opt.Data, Priority: opt.Priority, Status: WaitStatus, - Stacktrace: []string{}, queue: queue, RetryFailures: queue.config.RetryFailures, } @@ -53,7 +52,6 @@ func (queue *Queue) delayJob(opt AddJobOptions) *Job { Data: opt.Data, Priority: opt.Priority, Status: DelayedStatus, - Stacktrace: []string{}, queue: queue, RetryFailures: queue.config.RetryFailures, } diff --git a/queue.go b/queue.go index 6c4af2b..ecf5775 100644 --- a/queue.go +++ b/queue.go @@ -122,8 +122,10 @@ func (q *Queue) AddJob(opt AddJobOptions) { insertIdx := sort.Search(len(q.jobs), func(i int) bool { return q.jobs[i].Priority < job.Priority }) - // Efficient insertion: grow slice and insert at correct position - q.jobs = append(q.jobs[:insertIdx], append([]Job{*job}, q.jobs[insertIdx:]...)...) + // Efficient insertion: grow slice by one, shift elements, and insert at correct position + q.jobs = append(q.jobs, Job{}) + copy(q.jobs[insertIdx+1:], q.jobs[insertIdx:]) + q.jobs[insertIdx] = *job q.Run() } @@ -212,7 +214,8 @@ func (q *Queue) Run() { q.formatLog(LoggerError, "Error when lock mutex: %v", err) return } - execJobs := []*Job{} + // Pre-allocate with estimated capacity to reduce allocations + execJobs := make([]*Job, 0, len(q.jobs)) for i := range q.jobs { if q.jobs[i].IsReady() { execJobs = append(execJobs, &q.jobs[i]) @@ -281,7 +284,8 @@ func (q *Queue) Run() { // and removes it from the list of jobs to retry. Finally, it unlocks the mutex. func (q *Queue) Retry() { - execJobs := []*Job{} + // Pre-allocate with estimated capacity to reduce allocations + execJobs := make([]*Job, 0, len(q.jobs)) // For retry failures for i := range q.jobs { if q.jobs[i].Status == DelayedStatus { @@ -303,7 +307,8 @@ func (q *Queue) Retry() { var wg sync.WaitGroup done := make(chan struct{}) - var finishedJob []string + // Pre-allocate finishedJob slice with expected capacity + finishedJob := make([]string, 0, min) var finishedMu sync.Mutex for i := range numJobs { job := numJobs[i] @@ -335,7 +340,7 @@ func (q *Queue) Retry() { if len(finishedJob) > 0 { for _, id := range finishedJob { if len(execJobs) == 1 && execJobs[0].Id == id { - execJobs = []*Job{} + execJobs = execJobs[:0] break } idx := slices.IndexFunc(execJobs, func(j *Job) bool { return j.Id == id }) From 634e17add4b31fa6bfb12947bdd6b86823caa8ad Mon Sep 17 00:00:00 2001 From: Ren0503 Date: Sat, 17 Jan 2026 00:01:52 +0700 Subject: [PATCH 3/3] feat(queue): add error tracking to job stacktrace - Append failed reason to stacktrace in HandlerError method - Improves error debugging by maintaining error history --- job.go | 1 + 1 file changed, 1 insertion(+) diff --git a/job.go b/job.go index 2fb6ae4..0b06d88 100644 --- a/job.go +++ b/job.go @@ -90,6 +90,7 @@ func (job *Job) Process(cb Callback) { func (job *Job) HandlerError(reasonError string) { job.FailedReason = reasonError job.Status = FailedStatus + job.Stacktrace = append(job.Stacktrace, reasonError) // Store error if job.RetryFailures <= 0 {