mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
91 lines
2.7 KiB
Go
91 lines
2.7 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package routing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hanzoai/git/modules/graceful"
|
|
"github.com/hanzoai/git/modules/log"
|
|
"github.com/hanzoai/git/modules/process"
|
|
)
|
|
|
|
// Event indicates when the printer is triggered
|
|
type Event int
|
|
|
|
const (
|
|
// StartEvent at the beginning of a request
|
|
StartEvent Event = iota
|
|
|
|
// StillExecutingEvent the request is still executing
|
|
StillExecutingEvent
|
|
|
|
// EndEvent the request has ended (either completed or failed)
|
|
EndEvent
|
|
)
|
|
|
|
// logPrinterFunc is used to output the log for a request
|
|
type logPrinterFunc func(trigger Event, record *requestRecord)
|
|
|
|
type loggerRequestManager struct {
|
|
logPrint logPrinterFunc
|
|
reqRecords sync.Map // it only contains the active requests which haven't been detected as "slow"
|
|
}
|
|
|
|
func (manager *loggerRequestManager) startSlowQueryDetector(threshold time.Duration) {
|
|
go graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
|
|
ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: SlowQueryDetector", process.SystemProcessType, true)
|
|
defer finished()
|
|
// This go-routine checks all active requests every second.
|
|
// If a request has been running for a long time (eg: /user/events), we also print a log with "still-executing" message
|
|
// After the "still-executing" log is printed, the record will be removed from the map to prevent from duplicated logs in future
|
|
// We do not care about accurate duration here. It just does the check periodically, 0.5s or 1.5s are all OK.
|
|
t := time.NewTicker(time.Second)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-t.C:
|
|
now := time.Now()
|
|
|
|
// print logs for slow requests
|
|
manager.reqRecords.Range(func(key, value any) bool {
|
|
index, record := key.(uint64), value.(*requestRecord)
|
|
if now.Sub(record.startTime) >= threshold {
|
|
manager.logPrint(StillExecutingEvent, record)
|
|
manager.reqRecords.Delete(index)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func (manager *loggerRequestManager) handleRequestRecord(record *requestRecord) func() {
|
|
manager.reqRecords.Store(record.index, record)
|
|
manager.logPrint(StartEvent, record)
|
|
|
|
return func() {
|
|
// just in case there is a panic. now the panics are all recovered in middleware.go
|
|
localPanicErr := recover()
|
|
if localPanicErr != nil {
|
|
record.lock.Lock()
|
|
record.panicError = fmt.Errorf("%v\n%s", localPanicErr, log.Stack(2))
|
|
record.lock.Unlock()
|
|
}
|
|
|
|
manager.reqRecords.Delete(record.index)
|
|
manager.logPrint(EndEvent, record)
|
|
|
|
if localPanicErr != nil {
|
|
// the panic wasn't recovered before us, so we should pass it up, and let the framework handle the panic error
|
|
panic(localPanicErr)
|
|
}
|
|
}
|
|
}
|