mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
chore(main): 回流 release/v2.0.x(v2.0.12)—— 内容对齐,main 无独有内容
按发布纪律回流。**注意这是内容对齐而非 merge**,理由与依据:
1. 纪律禁止 merge 回主干,因为 merge 会把发布分支的**版本号变更**带进 main。
本项目版本号**只由 `git describe --tags` 推导**(Makefile VERSION),
仓库里没有任何文件存放版本号 ⇒ 回流不存在版本污染风险。
2. 逐项核对确认 **main 是 release 的内容严格子集**:
- `main` 独有的**文件**:0 个;
- `internal/lua/plugins.go` / `internal/core/kernel_actions.go` 等内核文件
main 独有行数:0;
- `deploy.sh` 那 14 行只是 emoji 清理(release 刻意去掉了 emoji);
- `channels.lua` 那 333 行是**旧版内容**(旧 exports 表、旧模型名单)。
- main 独有的 9 个提交按 patch-id **全部已在 release 中等效存在**
(`git cherry` 逐条列出均为 `-`,即已合入)。
3. 故回流 = 把 release 的内容带到 main,等价于"只捡拾修复、不带版本号",
且不会丢任何 main 独有的修复。
本次带回 v2.0.12 的全部内容(37 个提交):
- 源串行 + 按模型节流(照 model-gate)
- 已失效模型存储 + 远端目录闸门
- 余额分档选号
- 模型能力元数据(models.dev,isFree 权威判定)
- **凭据续期修复**(此前恒报 ok:true 却一个请求都不发)
- 通用件 TTL 缓存 + 串行锁;account_test/account_adopt/account_delete 修正
- 内核级:出站授权持久化重启后不生效、`__llmsproxy_plugin_dir` ground truth
校验:`git diff release/v2.0.x` = 0 行;`go build -tags luajit ./...` 通过。
This commit is contained in:
@ -55,6 +55,18 @@ type ScheduledTask struct {
|
||||
// Jitter, in seconds, spreads the first fire randomly so many plugins (or
|
||||
// many replicas) do not all hit the same upstream at :00.
|
||||
Jitter int `json:"jitter,omitempty"`
|
||||
// FirstDueNow makes the FIRST run happen shortly after boot instead of after
|
||||
// a full period.
|
||||
//
|
||||
// Why it exists (measured defect, borrowed from the reference implementation's
|
||||
// post-mortem): with interval-only scheduling a restart leaves short-lived
|
||||
// credentials expired for up to a whole period. The reference's providers
|
||||
// have cline at 1h, codearts at ~2h and raccoon at 3h, so a 30-minute
|
||||
// refresh interval left them showing "已过期" and their quota rows returning
|
||||
// 401 after every host restart. A startup catch-up round fixes that — but it
|
||||
// must be opt-in, because a task that is genuinely periodic (a daily
|
||||
// check-in, say) should NOT fire again just because the host restarted.
|
||||
FirstDueNow bool `json:"first_due_now,omitempty"`
|
||||
// Timeout is parsed for forward compatibility but is NOT enforced: a running
|
||||
// Lua call cannot be interrupted in this LuaJIT build (see the capability
|
||||
// note on checkWedged). Overrun is handled by disarming the plugin at the
|
||||
@ -106,6 +118,7 @@ func parseSchedule(L *golua.State) scheduleSpec {
|
||||
t.At = readString(L, -1, "at")
|
||||
t.Jitter = readNumberInt(L, -1, "jitter")
|
||||
t.Timeout = readNumberInt(L, -1, "timeout")
|
||||
t.FirstDueNow = readBoolField(L, -1, "first_due_now")
|
||||
if t.Fn != "" && (t.Every > 0 || t.At != "") {
|
||||
spec.tasks = append(spec.tasks, t)
|
||||
}
|
||||
@ -195,6 +208,12 @@ func (ps *Plugins) startRuntime(p *Plugin, spec scheduleSpec) *PluginRuntime {
|
||||
// plugins do not all wake at process start.
|
||||
func (rt *PluginRuntime) firstDue(t ScheduledTask) time.Time {
|
||||
now := time.Now()
|
||||
// A catch-up run: fire shortly after boot so a restart does not leave
|
||||
// short-lived credentials expired for a whole period (see FirstDueNow).
|
||||
// The short delay lets the rest of the plugin finish booting first.
|
||||
if t.FirstDueNow {
|
||||
return now.Add(catchUpDelay)
|
||||
}
|
||||
if t.At != "" {
|
||||
return nextDailyOccurrence(t.At, now)
|
||||
}
|
||||
@ -319,6 +338,11 @@ func (rt *PluginRuntime) runDue() {
|
||||
}
|
||||
}
|
||||
|
||||
// catchUpDelay is how long a FirstDueNow task waits after boot. Long enough for
|
||||
// the plugin's own boot work to finish, short enough that a restart heals
|
||||
// expired credentials promptly rather than after a full period.
|
||||
const catchUpDelay = 5 * time.Second
|
||||
|
||||
// nextAfter computes the following due time for a task that just fired.
|
||||
func (rt *PluginRuntime) nextAfter(t ScheduledTask, now time.Time) time.Time {
|
||||
if t.At != "" {
|
||||
@ -489,6 +513,17 @@ func randInt63n(n int64) int64 {
|
||||
|
||||
// readNumberInt reads a numeric field as an int. Missing/zero yields 0, which
|
||||
// every caller treats as "not set" (a task with every<=0 and no at is disabled).
|
||||
// readBoolField reads a boolean field, treating only a real Lua boolean as set.
|
||||
// A missing field (nil) means false — the caller's declared default.
|
||||
func readBoolField(L *golua.State, idx int, field string) bool {
|
||||
L.GetField(idx, field)
|
||||
defer L.Pop(1)
|
||||
if L.Type(-1) != golua.LUA_TBOOLEAN {
|
||||
return false
|
||||
}
|
||||
return L.ToBoolean(-1)
|
||||
}
|
||||
|
||||
func readNumberInt(L *golua.State, idx int, field string) int {
|
||||
L.GetField(idx, field)
|
||||
defer L.Pop(1)
|
||||
|
||||
Reference in New Issue
Block a user