@convex-dev/workpool 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/client/index.d.ts +1 -1
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/component/_generated/api.d.ts +1 -3
- package/dist/commonjs/component/_generated/api.d.ts.map +1 -1
- package/dist/commonjs/component/complete.js.map +1 -1
- package/dist/commonjs/component/crons.js.map +1 -1
- package/dist/commonjs/component/kick.js.map +1 -1
- package/dist/commonjs/component/lib.js.map +1 -1
- package/dist/commonjs/component/logging.js.map +1 -1
- package/dist/commonjs/component/loop.d.ts.map +1 -1
- package/dist/commonjs/component/loop.js +10 -2
- package/dist/commonjs/component/loop.js.map +1 -1
- package/dist/commonjs/component/recovery.js.map +1 -1
- package/dist/commonjs/component/schema.js +6 -6
- package/dist/commonjs/component/schema.js.map +1 -1
- package/dist/commonjs/component/shared.js +1 -1
- package/dist/commonjs/component/shared.js.map +1 -1
- package/dist/commonjs/component/stats.js.map +1 -1
- package/dist/commonjs/component/worker.js.map +1 -1
- package/dist/esm/client/index.d.ts +1 -1
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/component/_generated/api.d.ts +1 -3
- package/dist/esm/component/_generated/api.d.ts.map +1 -1
- package/dist/esm/component/complete.js.map +1 -1
- package/dist/esm/component/crons.js.map +1 -1
- package/dist/esm/component/kick.js.map +1 -1
- package/dist/esm/component/lib.js.map +1 -1
- package/dist/esm/component/logging.js.map +1 -1
- package/dist/esm/component/loop.d.ts.map +1 -1
- package/dist/esm/component/loop.js +10 -2
- package/dist/esm/component/loop.js.map +1 -1
- package/dist/esm/component/recovery.js.map +1 -1
- package/dist/esm/component/schema.js +6 -6
- package/dist/esm/component/schema.js.map +1 -1
- package/dist/esm/component/shared.js +1 -1
- package/dist/esm/component/shared.js.map +1 -1
- package/dist/esm/component/stats.js.map +1 -1
- package/dist/esm/component/worker.js.map +1 -1
- package/package.json +3 -3
- package/src/component/_generated/api.d.ts +1 -0
- package/src/component/loop.test.ts +79 -153
- package/src/component/loop.ts +9 -2
|
@@ -17,8 +17,10 @@ import {
|
|
|
17
17
|
DEFAULT_MAX_PARALLELISM,
|
|
18
18
|
getCurrentSegment,
|
|
19
19
|
getNextSegment,
|
|
20
|
+
HOUR,
|
|
20
21
|
toSegment,
|
|
21
22
|
} from "./shared";
|
|
23
|
+
import { DEFAULT_LOG_LEVEL } from "./logging";
|
|
22
24
|
|
|
23
25
|
const modules = import.meta.glob("./**/*.ts");
|
|
24
26
|
|
|
@@ -32,9 +34,17 @@ describe("loop", () => {
|
|
|
32
34
|
|
|
33
35
|
async function setMaxParallelism(maxParallelism: number) {
|
|
34
36
|
await t.run(async (ctx) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const globals = await ctx.db.query("globals").unique();
|
|
38
|
+
if (!globals) {
|
|
39
|
+
await ctx.db.insert("globals", {
|
|
40
|
+
logLevel: DEFAULT_LOG_LEVEL,
|
|
41
|
+
maxParallelism,
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
await ctx.db.patch(globals._id, {
|
|
45
|
+
maxParallelism,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
38
48
|
});
|
|
39
49
|
}
|
|
40
50
|
|
|
@@ -574,20 +584,7 @@ describe("loop", () => {
|
|
|
574
584
|
it("should handle generation mismatch", async () => {
|
|
575
585
|
// Setup state with different generation
|
|
576
586
|
await t.run(async (ctx) => {
|
|
577
|
-
await ctx
|
|
578
|
-
generation: 2n,
|
|
579
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
580
|
-
lastRecovery: 0n,
|
|
581
|
-
report: {
|
|
582
|
-
completed: 0,
|
|
583
|
-
succeeded: 0,
|
|
584
|
-
failed: 0,
|
|
585
|
-
retries: 0,
|
|
586
|
-
canceled: 0,
|
|
587
|
-
lastReportTs: Date.now(),
|
|
588
|
-
},
|
|
589
|
-
running: [],
|
|
590
|
-
});
|
|
587
|
+
await insertInternalState(ctx, { generation: 2n });
|
|
591
588
|
});
|
|
592
589
|
|
|
593
590
|
// Call main with mismatched generation
|
|
@@ -606,25 +603,8 @@ describe("loop", () => {
|
|
|
606
603
|
const scheduledId = await makeDummyScheduledFunction(ctx, workId);
|
|
607
604
|
|
|
608
605
|
// Create internal state
|
|
609
|
-
await ctx
|
|
610
|
-
|
|
611
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
612
|
-
lastRecovery: 0n,
|
|
613
|
-
report: {
|
|
614
|
-
completed: 0,
|
|
615
|
-
succeeded: 0,
|
|
616
|
-
failed: 0,
|
|
617
|
-
retries: 0,
|
|
618
|
-
canceled: 0,
|
|
619
|
-
lastReportTs: Date.now(),
|
|
620
|
-
},
|
|
621
|
-
running: [
|
|
622
|
-
{
|
|
623
|
-
workId,
|
|
624
|
-
scheduledId,
|
|
625
|
-
started: 900000,
|
|
626
|
-
},
|
|
627
|
-
],
|
|
606
|
+
await insertInternalState(ctx, {
|
|
607
|
+
running: [{ workId, scheduledId, started: 900000 }],
|
|
628
608
|
});
|
|
629
609
|
|
|
630
610
|
// Create pending completion
|
|
@@ -672,18 +652,7 @@ describe("loop", () => {
|
|
|
672
652
|
const scheduledId = await makeDummyScheduledFunction(ctx, workId);
|
|
673
653
|
|
|
674
654
|
// Create internal state
|
|
675
|
-
await ctx
|
|
676
|
-
generation: 1n,
|
|
677
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
678
|
-
lastRecovery: 0n,
|
|
679
|
-
report: {
|
|
680
|
-
completed: 0,
|
|
681
|
-
succeeded: 0,
|
|
682
|
-
failed: 0,
|
|
683
|
-
retries: 0,
|
|
684
|
-
canceled: 0,
|
|
685
|
-
lastReportTs: Date.now(),
|
|
686
|
-
},
|
|
655
|
+
await insertInternalState(ctx, {
|
|
687
656
|
running: [
|
|
688
657
|
{
|
|
689
658
|
workId,
|
|
@@ -743,25 +712,8 @@ describe("loop", () => {
|
|
|
743
712
|
);
|
|
744
713
|
|
|
745
714
|
// Create internal state
|
|
746
|
-
await ctx
|
|
747
|
-
|
|
748
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
749
|
-
lastRecovery: 0n,
|
|
750
|
-
report: {
|
|
751
|
-
completed: 0,
|
|
752
|
-
succeeded: 0,
|
|
753
|
-
failed: 0,
|
|
754
|
-
retries: 0,
|
|
755
|
-
canceled: 0,
|
|
756
|
-
lastReportTs: Date.now(),
|
|
757
|
-
},
|
|
758
|
-
running: [
|
|
759
|
-
{
|
|
760
|
-
workId: runningWorkId,
|
|
761
|
-
scheduledId,
|
|
762
|
-
started: 900000,
|
|
763
|
-
},
|
|
764
|
-
],
|
|
715
|
+
await insertInternalState(ctx, {
|
|
716
|
+
running: [{ workId: runningWorkId, scheduledId, started: 900000 }],
|
|
765
717
|
});
|
|
766
718
|
|
|
767
719
|
// Create work
|
|
@@ -816,20 +768,7 @@ describe("loop", () => {
|
|
|
816
768
|
// Setup state with pending start items
|
|
817
769
|
const workId = await t.run<Id<"work">>(async (ctx) => {
|
|
818
770
|
// Create internal state
|
|
819
|
-
await ctx
|
|
820
|
-
generation: 1n,
|
|
821
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
822
|
-
lastRecovery: 0n,
|
|
823
|
-
report: {
|
|
824
|
-
completed: 0,
|
|
825
|
-
succeeded: 0,
|
|
826
|
-
failed: 0,
|
|
827
|
-
retries: 0,
|
|
828
|
-
canceled: 0,
|
|
829
|
-
lastReportTs: Date.now(),
|
|
830
|
-
},
|
|
831
|
-
running: [],
|
|
832
|
-
});
|
|
771
|
+
await insertInternalState(ctx);
|
|
833
772
|
|
|
834
773
|
// Create work
|
|
835
774
|
const workId = await makeDummyWork(ctx);
|
|
@@ -872,25 +811,9 @@ describe("loop", () => {
|
|
|
872
811
|
const scheduledId = await makeDummyScheduledFunction(ctx, workId);
|
|
873
812
|
|
|
874
813
|
// Create internal state with old job
|
|
875
|
-
await ctx
|
|
876
|
-
generation: 1n,
|
|
877
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
814
|
+
await insertInternalState(ctx, {
|
|
878
815
|
lastRecovery: 0n,
|
|
879
|
-
|
|
880
|
-
completed: 0,
|
|
881
|
-
succeeded: 0,
|
|
882
|
-
failed: 0,
|
|
883
|
-
retries: 0,
|
|
884
|
-
canceled: 0,
|
|
885
|
-
lastReportTs: Date.now(),
|
|
886
|
-
},
|
|
887
|
-
running: [
|
|
888
|
-
{
|
|
889
|
-
workId,
|
|
890
|
-
scheduledId,
|
|
891
|
-
started: oldTime,
|
|
892
|
-
},
|
|
893
|
-
],
|
|
816
|
+
running: [{ workId, scheduledId, started: oldTime }],
|
|
894
817
|
});
|
|
895
818
|
});
|
|
896
819
|
|
|
@@ -918,20 +841,7 @@ describe("loop", () => {
|
|
|
918
841
|
it("should handle generation mismatch", async () => {
|
|
919
842
|
// Setup state with different generation
|
|
920
843
|
await t.run(async (ctx) => {
|
|
921
|
-
await ctx
|
|
922
|
-
generation: 2n,
|
|
923
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
924
|
-
lastRecovery: 0n,
|
|
925
|
-
report: {
|
|
926
|
-
completed: 0,
|
|
927
|
-
succeeded: 0,
|
|
928
|
-
failed: 0,
|
|
929
|
-
retries: 0,
|
|
930
|
-
canceled: 0,
|
|
931
|
-
lastReportTs: Date.now(),
|
|
932
|
-
},
|
|
933
|
-
running: [],
|
|
934
|
-
});
|
|
844
|
+
await insertInternalState(ctx, { generation: 2n });
|
|
935
845
|
});
|
|
936
846
|
|
|
937
847
|
// Call updateRunStatus with mismatched generation
|
|
@@ -950,20 +860,7 @@ describe("loop", () => {
|
|
|
950
860
|
const workId = await makeDummyWork(ctx);
|
|
951
861
|
|
|
952
862
|
// Create internal state
|
|
953
|
-
await ctx
|
|
954
|
-
generation: 1n,
|
|
955
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
956
|
-
lastRecovery: 0n,
|
|
957
|
-
report: {
|
|
958
|
-
completed: 0,
|
|
959
|
-
succeeded: 0,
|
|
960
|
-
failed: 0,
|
|
961
|
-
retries: 0,
|
|
962
|
-
canceled: 0,
|
|
963
|
-
lastReportTs: Date.now(),
|
|
964
|
-
},
|
|
965
|
-
running: [],
|
|
966
|
-
});
|
|
863
|
+
await insertInternalState(ctx, {});
|
|
967
864
|
|
|
968
865
|
// Create run status
|
|
969
866
|
await ctx.db.insert("runStatus", {
|
|
@@ -998,20 +895,7 @@ describe("loop", () => {
|
|
|
998
895
|
// Setup state with no work
|
|
999
896
|
await t.run(async (ctx) => {
|
|
1000
897
|
// Create internal state with no running jobs
|
|
1001
|
-
await ctx
|
|
1002
|
-
generation: 1n,
|
|
1003
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
1004
|
-
lastRecovery: 0n,
|
|
1005
|
-
report: {
|
|
1006
|
-
completed: 0,
|
|
1007
|
-
succeeded: 0,
|
|
1008
|
-
failed: 0,
|
|
1009
|
-
retries: 0,
|
|
1010
|
-
canceled: 0,
|
|
1011
|
-
lastReportTs: Date.now(),
|
|
1012
|
-
},
|
|
1013
|
-
running: [],
|
|
1014
|
-
});
|
|
898
|
+
await insertInternalState(ctx, {});
|
|
1015
899
|
|
|
1016
900
|
// Create run status in running state
|
|
1017
901
|
await ctx.db.insert("runStatus", {
|
|
@@ -1056,18 +940,7 @@ describe("loop", () => {
|
|
|
1056
940
|
);
|
|
1057
941
|
|
|
1058
942
|
// Create internal state with max running jobs
|
|
1059
|
-
await ctx
|
|
1060
|
-
generation: 1n,
|
|
1061
|
-
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
1062
|
-
lastRecovery: now,
|
|
1063
|
-
report: {
|
|
1064
|
-
completed: 0,
|
|
1065
|
-
succeeded: 0,
|
|
1066
|
-
failed: 0,
|
|
1067
|
-
retries: 0,
|
|
1068
|
-
canceled: 0,
|
|
1069
|
-
lastReportTs: Date.now(),
|
|
1070
|
-
},
|
|
943
|
+
await insertInternalState(ctx, {
|
|
1071
944
|
running: runningJobs,
|
|
1072
945
|
});
|
|
1073
946
|
|
|
@@ -1100,6 +973,59 @@ describe("loop", () => {
|
|
|
1100
973
|
expect(runStatus!.state.saturated).toBe(true);
|
|
1101
974
|
});
|
|
1102
975
|
});
|
|
976
|
+
|
|
977
|
+
it("should reset cursors correctly when there's old work detected", async () => {
|
|
978
|
+
// Setup state with old work
|
|
979
|
+
const now = getCurrentSegment();
|
|
980
|
+
await t.run(async (ctx) => {
|
|
981
|
+
// Create internal state with old work
|
|
982
|
+
await insertInternalState(ctx, {
|
|
983
|
+
segmentCursors: {
|
|
984
|
+
incoming: now - 1n,
|
|
985
|
+
completion: now - 1n,
|
|
986
|
+
cancelation: now - 1n,
|
|
987
|
+
},
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
// Insert very old work
|
|
992
|
+
await t.run(async (ctx) => {
|
|
993
|
+
const workId = await makeDummyWork(ctx);
|
|
994
|
+
await ctx.db.insert("pendingStart", {
|
|
995
|
+
workId,
|
|
996
|
+
segment: 0n,
|
|
997
|
+
});
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// Call updateRunStatus
|
|
1001
|
+
await t.mutation(internal.loop.updateRunStatus, {
|
|
1002
|
+
generation: 1n,
|
|
1003
|
+
segment: now,
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
// Verify cursors were reset
|
|
1007
|
+
await t.run(async (ctx) => {
|
|
1008
|
+
const state = await ctx.db.query("internalState").unique();
|
|
1009
|
+
expect(state).toBeDefined();
|
|
1010
|
+
expect(state!.segmentCursors.incoming).toBe(0n);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// Set maxParallelism to 0 so it doesn't schedule anything / make progress
|
|
1014
|
+
await setMaxParallelism(0);
|
|
1015
|
+
|
|
1016
|
+
// Run main
|
|
1017
|
+
await t.mutation(internal.loop.main, {
|
|
1018
|
+
generation: 1n,
|
|
1019
|
+
segment: now,
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
// Verify start cursor weren't updated
|
|
1023
|
+
await t.run(async (ctx) => {
|
|
1024
|
+
const state = await ctx.db.query("internalState").unique();
|
|
1025
|
+
expect(state).toBeDefined();
|
|
1026
|
+
expect(state!.segmentCursors.incoming).toBe(0n);
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1103
1029
|
});
|
|
1104
1030
|
|
|
1105
1031
|
describe("complete function", () => {
|
package/src/component/loop.ts
CHANGED
|
@@ -28,7 +28,7 @@ const SECOND = 1000;
|
|
|
28
28
|
const MINUTE = 60 * SECOND;
|
|
29
29
|
const RECOVERY_THRESHOLD_MS = 5 * MINUTE; // attempt to recover jobs this old.
|
|
30
30
|
export const RECOVERY_PERIOD_SEGMENTS = toSegment(1 * MINUTE); // how often to check.
|
|
31
|
-
const CURSOR_BUFFER_SEGMENTS = toSegment(
|
|
31
|
+
const CURSOR_BUFFER_SEGMENTS = toSegment(30 * SECOND); // buffer for cursor updates.
|
|
32
32
|
export const INITIAL_STATE: WithoutSystemFields<Doc<"internalState">> = {
|
|
33
33
|
generation: 0n,
|
|
34
34
|
segmentCursors: { incoming: 0n, completion: 0n, cancelation: 0n },
|
|
@@ -521,7 +521,14 @@ async function handleStart(
|
|
|
521
521
|
.take(toSchedule)
|
|
522
522
|
: [];
|
|
523
523
|
|
|
524
|
-
|
|
524
|
+
if (pending) {
|
|
525
|
+
if (pending.length > 0) {
|
|
526
|
+
state.segmentCursors.incoming = pending.at(-1)!.segment;
|
|
527
|
+
} else if (toSchedule > 0) {
|
|
528
|
+
// We have no more pending work, update to now
|
|
529
|
+
state.segmentCursors.incoming = segment;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
525
532
|
console.debug(`[main] scheduling ${pending.length} pending work`);
|
|
526
533
|
// Start new work.
|
|
527
534
|
state.running.push(
|