mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix(discord): prune idle rest route mappings
This commit is contained in:
@@ -89,6 +89,7 @@ export class RestScheduler<TData> {
|
||||
return {
|
||||
globalRateLimitUntil: this.globalRateLimitUntil,
|
||||
activeBuckets: this.buckets.size,
|
||||
routeBucketMappings: this.routeBuckets.size,
|
||||
buckets: Array.from(this.buckets.entries()).map(([key, bucket]) => ({
|
||||
key,
|
||||
active: bucket.active,
|
||||
@@ -144,6 +145,34 @@ export class RestScheduler<TData> {
|
||||
return false;
|
||||
}
|
||||
|
||||
private isBucketRateLimited(bucket: BucketState<TData>, now = Date.now()): boolean {
|
||||
return bucket.remaining === 0 && bucket.resetAt > now;
|
||||
}
|
||||
|
||||
private pruneRouteMapping(routeKey: string): void {
|
||||
const bucketKey = this.routeBuckets.get(routeKey);
|
||||
if (!bucketKey) {
|
||||
return;
|
||||
}
|
||||
this.routeBuckets.delete(routeKey);
|
||||
this.buckets.get(bucketKey)?.routeKeys.delete(routeKey);
|
||||
}
|
||||
|
||||
private pruneIdleRouteMappings(
|
||||
bucketKey: string,
|
||||
bucket: BucketState<TData>,
|
||||
now = Date.now(),
|
||||
): void {
|
||||
if (bucket.active > 0 || bucket.pending.length > 0 || this.isBucketRateLimited(bucket, now)) {
|
||||
return;
|
||||
}
|
||||
for (const routeKey of Array.from(bucket.routeKeys)) {
|
||||
if (this.routeBuckets.get(routeKey) === bucketKey) {
|
||||
this.pruneRouteMapping(routeKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private shouldPruneIdleBucket(key: string): boolean {
|
||||
const mappedBucketKey = this.routeBuckets.get(key);
|
||||
return mappedBucketKey !== key && !this.hasBucketReference(key);
|
||||
@@ -267,7 +296,15 @@ export class RestScheduler<TData> {
|
||||
break;
|
||||
}
|
||||
if (bucket.pending.length === 0) {
|
||||
if (bucket.active === 0 && this.shouldPruneIdleBucket(key)) {
|
||||
if (bucket.active !== 0) {
|
||||
continue;
|
||||
}
|
||||
if (this.isBucketRateLimited(bucket, now)) {
|
||||
nextDelayMs = Math.min(nextDelayMs, bucket.resetAt - now);
|
||||
continue;
|
||||
}
|
||||
this.pruneIdleRouteMappings(key, bucket, now);
|
||||
if (this.shouldPruneIdleBucket(key)) {
|
||||
this.buckets.delete(key);
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -83,7 +83,7 @@ describe("RequestClient", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("prunes idle route buckets after Discord bucket remapping", async () => {
|
||||
it("prunes idle route buckets and mappings after Discord bucket remapping", async () => {
|
||||
const client = new RequestClient("test-token", {
|
||||
fetch: async () =>
|
||||
new Response(JSON.stringify({ id: "first" }), {
|
||||
@@ -95,8 +95,9 @@ describe("RequestClient", () => {
|
||||
await expect(client.get("/channels/c1/messages")).resolves.toEqual({ id: "first" });
|
||||
|
||||
const metrics = client.getSchedulerMetrics();
|
||||
expect(metrics.activeBuckets).toBe(1);
|
||||
expect(metrics.buckets.map((bucket) => bucket.key)).toEqual(["channel-messages:channels/c1"]);
|
||||
expect(metrics.activeBuckets).toBe(0);
|
||||
expect(metrics.routeBucketMappings).toBe(0);
|
||||
expect(metrics.buckets).toEqual([]);
|
||||
});
|
||||
|
||||
it("waits for a learned bucket reset before dispatching the next request", async () => {
|
||||
@@ -135,6 +136,7 @@ describe("RequestClient", () => {
|
||||
const client = new RequestClient("test-token", { fetch: fetchSpy });
|
||||
|
||||
await expect(client.get("/channels/c1/messages")).resolves.toEqual({ id: "first" });
|
||||
expect(client.getSchedulerMetrics().routeBucketMappings).toBe(1);
|
||||
|
||||
const second = client.get("/channels/c1/messages");
|
||||
await Promise.resolve();
|
||||
|
||||
Reference in New Issue
Block a user