@effect-app/infra 1.40.0 → 1.41.0
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/CHANGELOG.md +12 -0
- package/_cjs/api/reportError.cjs +3 -29
- package/_cjs/api/reportError.cjs.map +1 -1
- package/_cjs/services/MainFiberSet.cjs +60 -0
- package/_cjs/services/MainFiberSet.cjs.map +1 -0
- package/_cjs/services/Operations.cjs +14 -14
- package/_cjs/services/Operations.cjs.map +1 -1
- package/_cjs/services/QueueMaker/errors.cjs +0 -19
- package/_cjs/services/QueueMaker/errors.cjs.map +1 -1
- package/_cjs/services/RequestFiberSet.cjs +83 -0
- package/_cjs/services/RequestFiberSet.cjs.map +1 -0
- package/dist/api/reportError.d.ts +2 -21
- package/dist/api/reportError.d.ts.map +1 -1
- package/dist/api/reportError.js +3 -29
- package/dist/services/MainFiberSet.d.ts +60 -0
- package/dist/services/MainFiberSet.d.ts.map +1 -0
- package/dist/services/MainFiberSet.js +53 -0
- package/dist/services/Operations.d.ts +26 -11
- package/dist/services/Operations.d.ts.map +1 -1
- package/dist/services/Operations.js +17 -14
- package/dist/services/QueueMaker/errors.d.ts +0 -14
- package/dist/services/QueueMaker/errors.d.ts.map +1 -1
- package/dist/services/QueueMaker/errors.js +1 -18
- package/dist/services/RequestFiberSet.d.ts +68 -0
- package/dist/services/RequestFiberSet.d.ts.map +1 -0
- package/dist/services/RequestFiberSet.js +77 -0
- package/package.json +23 -3
- package/src/api/reportError.ts +2 -40
- package/src/services/MainFiberSet.ts +70 -0
- package/src/services/Operations.ts +48 -45
- package/src/services/QueueMaker/errors.ts +0 -27
- package/src/services/RequestFiberSet.ts +104 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Context, Effect, Fiber, FiberSet, Layer } from "@effect-app/core"
|
|
2
|
+
import type {} from "effect/Scope"
|
|
3
|
+
import type {} from "effect/Context"
|
|
4
|
+
import { InfraLogger } from "../logger.js"
|
|
5
|
+
import { reportNonInterruptedFailureCause } from "./QueueMaker/errors.js"
|
|
6
|
+
import { setRootParentSpan } from "./RequestFiberSet.js"
|
|
7
|
+
|
|
8
|
+
const make = Effect.gen(function*() {
|
|
9
|
+
const set = yield* FiberSet.make<unknown, never>()
|
|
10
|
+
const add = (...fibers: Fiber.RuntimeFiber<never, never>[]) =>
|
|
11
|
+
Effect.sync(() => fibers.forEach((_) => FiberSet.unsafeAdd(set, _)))
|
|
12
|
+
const addAll = (fibers: readonly Fiber.RuntimeFiber<never, never>[]) =>
|
|
13
|
+
Effect.sync(() => fibers.forEach((_) => FiberSet.unsafeAdd(set, _)))
|
|
14
|
+
const join = FiberSet.size(set).pipe(
|
|
15
|
+
Effect.andThen((count) => InfraLogger.logDebug(`Joining ${count} current fibers on the MainFiberSet`)),
|
|
16
|
+
Effect.andThen(FiberSet.join(set))
|
|
17
|
+
)
|
|
18
|
+
const run = FiberSet.run(set)
|
|
19
|
+
|
|
20
|
+
// const waitUntilEmpty = Effect.gen(function*() {
|
|
21
|
+
// const currentSize = yield* FiberSet.size(set)
|
|
22
|
+
// if (currentSize === 0) {
|
|
23
|
+
// return
|
|
24
|
+
// }
|
|
25
|
+
// yield* InfraLogger.logInfo("Waiting MainFiberSet to be empty: " + currentSize)
|
|
26
|
+
// while ((yield* FiberSet.size(set)) > 0) yield* Effect.sleep("250 millis")
|
|
27
|
+
// yield* InfraLogger.logDebug("MainFiberSet is empty")
|
|
28
|
+
// })
|
|
29
|
+
|
|
30
|
+
// TODO: loop and interrupt all fibers in the set continuously?
|
|
31
|
+
const interrupt = Fiber.interruptAll(set)
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Forks the effect into a new fiber attached to the MainFiberSet scope. Because the
|
|
35
|
+
* new fiber isn't attached to the parent, when the fiber executing the
|
|
36
|
+
* returned effect terminates, the forked fiber will continue running.
|
|
37
|
+
* The fiber will be interrupted when the MainFiberSet scope is closed.
|
|
38
|
+
*
|
|
39
|
+
* The parent span is set to the root span of the current fiber.
|
|
40
|
+
* Reports and then swallows errors.
|
|
41
|
+
*/
|
|
42
|
+
function fork<A, E, R>(self: Effect<A, E, R>) {
|
|
43
|
+
return self.pipe(
|
|
44
|
+
Effect.asVoid,
|
|
45
|
+
Effect.catchAllCause(reportNonInterruptedFailureCause({})),
|
|
46
|
+
setRootParentSpan,
|
|
47
|
+
Effect.uninterruptible,
|
|
48
|
+
run
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
interrupt,
|
|
53
|
+
join,
|
|
54
|
+
fork,
|
|
55
|
+
run,
|
|
56
|
+
add,
|
|
57
|
+
addAll
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whenever you fork long running (e.g worker) fibers via e.g `Effect.forkScoped` or `Effect.forkDaemon`
|
|
63
|
+
* you should register these long running fibers in a FiberSet, and join them at the end of your main program.
|
|
64
|
+
* This way any errors will blow up the main program instead of fibers dying unknowingly.
|
|
65
|
+
*/
|
|
66
|
+
export class MainFiberSet extends Context.TagMakeId("MainFiberSet", make)<MainFiberSet>() {
|
|
67
|
+
static readonly Live = this.toLayerScoped()
|
|
68
|
+
static readonly JoinLive = this.pipe(Effect.andThen((_) => _.join), Layer.effectDiscard, Layer.provide(this.Live))
|
|
69
|
+
static readonly run = <A, R>(self: Effect<A, never, R>) => this.use((_) => _.run(self))
|
|
70
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { annotateLogscoped } from "@effect-app/core/Effect"
|
|
2
2
|
import { dual, pipe } from "@effect-app/core/Function"
|
|
3
|
-
import { RequestFiberSet } from "@effect-app/infra-adapters/RequestFiberSet"
|
|
4
3
|
import { reportError } from "@effect-app/infra/errorReporter"
|
|
5
4
|
import { NonEmptyString2k } from "@effect-app/schema"
|
|
6
5
|
import { subHours } from "date-fns"
|
|
@@ -8,12 +7,12 @@ import type { Fiber } from "effect-app"
|
|
|
8
7
|
import { Cause, Context, copy, Duration, Effect, Exit, Layer, Option, S, Schedule } from "effect-app"
|
|
9
8
|
import type { OperationProgress } from "effect-app/Operations"
|
|
10
9
|
import { Failure, Operation, OperationId, Success } from "effect-app/Operations"
|
|
11
|
-
import { MainFiberSet } from "effect-app/services/MainFiberSet"
|
|
12
10
|
import * as Scope from "effect/Scope"
|
|
13
|
-
import { forkDaemonReportRequestUnexpected } from "../api/reportError.js"
|
|
14
11
|
import { batch } from "../rateLimit.js"
|
|
12
|
+
import { MainFiberSet } from "./MainFiberSet.js"
|
|
15
13
|
import { OperationsRepo } from "./OperationsRepo.js"
|
|
16
14
|
import { where } from "./query.js"
|
|
15
|
+
import { RequestFiberSet } from "./RequestFiberSet.js"
|
|
17
16
|
|
|
18
17
|
const reportAppError = reportError("Operations.Cleanup")
|
|
19
18
|
|
|
@@ -97,10 +96,11 @@ const make = Effect.gen(function*() {
|
|
|
97
96
|
.pipe(
|
|
98
97
|
Scope.extend(scope),
|
|
99
98
|
Effect.flatMap((id) =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
reqFiberSet
|
|
100
|
+
.forkDaemonReportRequestUnexpected(Scope.use(
|
|
101
|
+
self(id).pipe(Effect.withSpan(title)),
|
|
102
|
+
scope
|
|
103
|
+
))
|
|
104
104
|
.pipe(Effect.map((fiber): RunningOperation<A, E> => ({ fiber, id })))
|
|
105
105
|
),
|
|
106
106
|
Effect.tap(({ id }) =>
|
|
@@ -133,21 +133,58 @@ const make = Effect.gen(function*() {
|
|
|
133
133
|
Scope.extend(scope),
|
|
134
134
|
Effect
|
|
135
135
|
.flatMap((id) =>
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
reqFiberSet
|
|
137
|
+
.forkDaemonReportRequestUnexpected(Scope.use(
|
|
138
|
+
self(id).pipe(Effect.withSpan(title)),
|
|
139
|
+
scope
|
|
140
|
+
))
|
|
140
141
|
.pipe(Effect.map((fiber): RunningOperation<A, E> => ({ fiber, id })))
|
|
141
142
|
)
|
|
142
143
|
)
|
|
143
144
|
)
|
|
144
145
|
)
|
|
145
146
|
|
|
147
|
+
const forkOperation: {
|
|
148
|
+
(title: NonEmptyString2k): <R, E, A>(
|
|
149
|
+
self: Effect<A, E, R>
|
|
150
|
+
) => Effect<RunningOperation<A, E>, never, Exclude<R, Scope.Scope>>
|
|
151
|
+
<R, E, A>(
|
|
152
|
+
self: Effect<A, E, R>,
|
|
153
|
+
title: NonEmptyString2k
|
|
154
|
+
): Effect<RunningOperation<A, E>, never, Exclude<R, Scope.Scope>>
|
|
155
|
+
} = dual(
|
|
156
|
+
2,
|
|
157
|
+
<R, E, A>(self: Effect<A, E, R>, title: NonEmptyString2k) =>
|
|
158
|
+
Effect.flatMap(
|
|
159
|
+
Scope.make(),
|
|
160
|
+
(scope) =>
|
|
161
|
+
register(title)
|
|
162
|
+
.pipe(
|
|
163
|
+
Scope.extend(scope),
|
|
164
|
+
Effect
|
|
165
|
+
.flatMap((id) =>
|
|
166
|
+
reqFiberSet
|
|
167
|
+
.forkDaemonReportRequestUnexpected(Scope.use(
|
|
168
|
+
self.pipe(Effect.withSpan(title)),
|
|
169
|
+
scope
|
|
170
|
+
))
|
|
171
|
+
.pipe(Effect.map((fiber): RunningOperation<A, E> => ({ fiber, id })))
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
function forkOperationFunction<R, E, A, Inp>(fnc: (inp: Inp) => Effect<A, E, R>, title: NonEmptyString2k) {
|
|
178
|
+
return (inp: Inp) => fnc(inp).pipe((_) => forkOperation(_, title))
|
|
179
|
+
}
|
|
180
|
+
|
|
146
181
|
return {
|
|
147
182
|
cleanup,
|
|
148
183
|
register,
|
|
149
184
|
fork,
|
|
150
185
|
fork2,
|
|
186
|
+
forkOperation,
|
|
187
|
+
forkOperationFunction,
|
|
151
188
|
all: repo.all,
|
|
152
189
|
find: findOp,
|
|
153
190
|
update
|
|
@@ -181,37 +218,3 @@ export interface RunningOperation<A, E> {
|
|
|
181
218
|
id: OperationId
|
|
182
219
|
fiber: Fiber.RuntimeFiber<A, E>
|
|
183
220
|
}
|
|
184
|
-
|
|
185
|
-
export const forkOperation: {
|
|
186
|
-
(title: NonEmptyString2k): <R, E, A>(
|
|
187
|
-
self: Effect<A, E, R>
|
|
188
|
-
) => Effect<RunningOperation<A, E>, never, Operations | Exclude<R, Scope.Scope>>
|
|
189
|
-
<R, E, A>(
|
|
190
|
-
self: Effect<A, E, R>,
|
|
191
|
-
title: NonEmptyString2k
|
|
192
|
-
): Effect<RunningOperation<A, E>, never, Operations | Exclude<R, Scope.Scope>>
|
|
193
|
-
} = dual(
|
|
194
|
-
2,
|
|
195
|
-
<R, E, A>(self: Effect<A, E, R>, title: NonEmptyString2k) =>
|
|
196
|
-
Effect.flatMap(
|
|
197
|
-
Scope.make(),
|
|
198
|
-
(scope) =>
|
|
199
|
-
Operations
|
|
200
|
-
.register(title)
|
|
201
|
-
.pipe(
|
|
202
|
-
Scope.extend(scope),
|
|
203
|
-
Effect
|
|
204
|
-
.flatMap((id) =>
|
|
205
|
-
forkDaemonReportRequestUnexpected(Scope.use(
|
|
206
|
-
self.pipe(Effect.withSpan(title)),
|
|
207
|
-
scope
|
|
208
|
-
))
|
|
209
|
-
.pipe(Effect.map((fiber): RunningOperation<A, E> => ({ fiber, id })))
|
|
210
|
-
)
|
|
211
|
-
)
|
|
212
|
-
)
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
export function forkOperationFunction<R, E, A, Inp>(fnc: (inp: Inp) => Effect<A, E, R>, title: NonEmptyString2k) {
|
|
216
|
-
return (inp: Inp) => fnc(inp).pipe((_) => forkOperation(_, title))
|
|
217
|
-
}
|
|
@@ -1,38 +1,11 @@
|
|
|
1
|
-
import { setRootParentSpan } from "@effect-app/infra-adapters/RequestFiberSet"
|
|
2
1
|
import { reportError } from "@effect-app/infra/errorReporter"
|
|
3
2
|
import { Cause, Effect, Exit } from "effect-app"
|
|
4
|
-
import { MainFiberSet } from "effect-app/services/MainFiberSet"
|
|
5
3
|
|
|
6
4
|
const reportQueueError_ = reportError("Queue")
|
|
7
5
|
|
|
8
6
|
export const reportQueueError = <E>(cause: Cause<E>, extras?: Record<string, unknown>) =>
|
|
9
7
|
reportQueueError_(cause, extras)
|
|
10
8
|
|
|
11
|
-
/**
|
|
12
|
-
* Forks the effect into a new fiber attached to the MainFiberSet scope. Because the
|
|
13
|
-
* new fiber isn't attached to the parent, when the fiber executing the
|
|
14
|
-
* returned effect terminates, the forked fiber will continue running.
|
|
15
|
-
* The fiber will be interrupted when the MainFiberSet scope is closed.
|
|
16
|
-
*
|
|
17
|
-
* The parent span is set to the root span of the current fiber.
|
|
18
|
-
* Reports and then swallows errors.
|
|
19
|
-
*
|
|
20
|
-
* @tsplus getter effect/io/Effect forkDaemonReportQueue
|
|
21
|
-
*/
|
|
22
|
-
export function forkDaemonReportQueue<A, E, R>(self: Effect<A, E, R>) {
|
|
23
|
-
return self.pipe(
|
|
24
|
-
Effect.asVoid,
|
|
25
|
-
Effect.catchAllCause(reportNonInterruptedFailureCause({})),
|
|
26
|
-
setRootParentSpan,
|
|
27
|
-
Effect.uninterruptible,
|
|
28
|
-
MainFiberSet.run
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const reportFatalQueueError = reportError(
|
|
33
|
-
"FatalQueue"
|
|
34
|
-
)
|
|
35
|
-
|
|
36
9
|
export function reportNonInterruptedFailure(context?: Record<string, unknown>) {
|
|
37
10
|
const report = reportNonInterruptedFailureCause(context)
|
|
38
11
|
return <A, E, R>(inp: Effect<A, E, R>): Effect<Exit<A, E>, never, R> =>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import type { Tracer } from "@effect-app/core"
|
|
3
|
+
import { Context, Effect, Fiber, FiberSet, Option } from "@effect-app/core"
|
|
4
|
+
import { reportRequestError, reportUnknownRequestError } from "../api/reportError.js"
|
|
5
|
+
import { InfraLogger } from "../logger.js"
|
|
6
|
+
|
|
7
|
+
const getRootParentSpan = Effect.gen(function*() {
|
|
8
|
+
let span: Tracer.AnySpan | null = yield* Effect.currentSpan.pipe(
|
|
9
|
+
Effect.catchTag("NoSuchElementException", () => Effect.succeed(null))
|
|
10
|
+
)
|
|
11
|
+
if (!span) return span
|
|
12
|
+
while (span._tag === "Span" && Option.isSome(span.parent)) {
|
|
13
|
+
span = span.parent.value
|
|
14
|
+
}
|
|
15
|
+
return span
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const setRootParentSpan = <A, E, R>(self: Effect<A, E, R>) =>
|
|
19
|
+
getRootParentSpan.pipe(Effect.andThen((span) => span ? Effect.withParentSpan(self, span) : self))
|
|
20
|
+
|
|
21
|
+
const make = Effect.gen(function*() {
|
|
22
|
+
const set = yield* FiberSet.make<any, any>()
|
|
23
|
+
const add = (...fibers: Fiber.RuntimeFiber<any, any>[]) =>
|
|
24
|
+
Effect.sync(() => fibers.forEach((_) => FiberSet.unsafeAdd(set, _)))
|
|
25
|
+
const addAll = (fibers: readonly Fiber.RuntimeFiber<any, any>[]) =>
|
|
26
|
+
Effect.sync(() => fibers.forEach((_) => FiberSet.unsafeAdd(set, _)))
|
|
27
|
+
const join = FiberSet.size(set).pipe(
|
|
28
|
+
Effect.andThen((count) => InfraLogger.logInfo(`Joining ${count} current fibers on the RequestFiberSet`)),
|
|
29
|
+
Effect.andThen(FiberSet.join(set))
|
|
30
|
+
)
|
|
31
|
+
const run = FiberSet.run(set)
|
|
32
|
+
const register = <A, E, R>(self: Effect<A, E, R>) =>
|
|
33
|
+
self.pipe(Effect.fork, Effect.tap(add), Effect.andThen(Fiber.join))
|
|
34
|
+
|
|
35
|
+
// const waitUntilEmpty = Effect.gen(function*() {
|
|
36
|
+
// const currentSize = yield* FiberSet.size(set)
|
|
37
|
+
// if (currentSize === 0) {
|
|
38
|
+
// return
|
|
39
|
+
// }
|
|
40
|
+
// yield* Effect.logInfo("Waiting RequestFiberSet to be empty: " + currentSize)
|
|
41
|
+
// while ((yield* FiberSet.size(set)) > 0) yield* Effect.sleep("250 millis")
|
|
42
|
+
// yield* Effect.logDebug("RequestFiberSet is empty")
|
|
43
|
+
// })
|
|
44
|
+
// TODO: loop and interrupt all fibers in the set continuously?
|
|
45
|
+
const interrupt = Fiber.interruptAll(set)
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Forks the effect into a new fiber attached to the RequestFiberSet scope. Because the
|
|
49
|
+
* new fiber isn't attached to the parent, when the fiber executing the
|
|
50
|
+
* returned effect terminates, the forked fiber will continue running.
|
|
51
|
+
* The fiber will be interrupted when the RequestFiberSet scope is closed.
|
|
52
|
+
*
|
|
53
|
+
* The parent span is set to the root span of the current fiber.
|
|
54
|
+
* Reports errors.
|
|
55
|
+
*/
|
|
56
|
+
function forkDaemonReportRequest<R, E, A>(self: Effect<A, E, R>) {
|
|
57
|
+
return self.pipe(
|
|
58
|
+
reportRequestError,
|
|
59
|
+
setRootParentSpan,
|
|
60
|
+
Effect.uninterruptible,
|
|
61
|
+
run
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Forks the effect into a new fiber attached to the RequestFiberSet scope. Because the
|
|
67
|
+
* new fiber isn't attached to the parent, when the fiber executing the
|
|
68
|
+
* returned effect terminates, the forked fiber will continue running.
|
|
69
|
+
* The fiber will be interrupted when the RequestFiberSet scope is closed.
|
|
70
|
+
*
|
|
71
|
+
* The parent span is set to the root span of the current fiber.
|
|
72
|
+
* Reports unexpected errors.
|
|
73
|
+
*/
|
|
74
|
+
function forkDaemonReportRequestUnexpected<R, E, A>(self: Effect<A, E, R>) {
|
|
75
|
+
return self
|
|
76
|
+
.pipe(
|
|
77
|
+
reportUnknownRequestError,
|
|
78
|
+
setRootParentSpan,
|
|
79
|
+
Effect.uninterruptible,
|
|
80
|
+
run
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
interrupt,
|
|
86
|
+
join,
|
|
87
|
+
run,
|
|
88
|
+
add,
|
|
89
|
+
addAll,
|
|
90
|
+
register,
|
|
91
|
+
forkDaemonReportRequest,
|
|
92
|
+
forkDaemonReportRequestUnexpected
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whenever you fork a fiber for a Request, and you want to prevent dependent services to close prematurely on interruption,
|
|
98
|
+
* like the ServiceBus Sender, you should register these fibers in this FiberSet.
|
|
99
|
+
*/
|
|
100
|
+
export class RequestFiberSet extends Context.TagMakeId("RequestFiberSet", make)<RequestFiberSet>() {
|
|
101
|
+
static readonly Live = this.toLayerScoped()
|
|
102
|
+
static readonly register = <A, E, R>(self: Effect<A, E, R>) => this.use((_) => _.register(self))
|
|
103
|
+
static readonly run = <A, E, R>(self: Effect<A, E, R>) => this.use((_) => _.run(self))
|
|
104
|
+
}
|