Skip to content

Commit

Permalink
add RcMap.touch, for reseting the idle timeout for an item (#4281)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored and effect-bot committed Jan 17, 2025
1 parent 12f4616 commit a9f2c0e
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/great-buses-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

add RcMap.touch, for reseting the idle timeout for an item
9 changes: 9 additions & 0 deletions packages/effect/src/RcMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,12 @@ export const invalidate: {
<K>(key: K): <A, E>(self: RcMap<K, A, E>) => Effect.Effect<void>
<K, A, E>(self: RcMap<K, A, E>, key: K): Effect.Effect<void>
} = internal.invalidate

/**
* @since 3.13.0
* @category combinators
*/
export const touch: {
<K>(key: K): <A, E>(self: RcMap<K, A, E>) => Effect.Effect<void>
<K, A, E>(self: RcMap<K, A, E>, key: K): Effect.Effect<void>
} = internal.touch
37 changes: 35 additions & 2 deletions packages/effect/src/internal/rcMap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type * as Cause from "../Cause.js"
import type { Clock } from "../Clock.js"
import * as Context from "../Context.js"
import type * as Deferred from "../Deferred.js"
import * as Duration from "../Duration.js"
Expand Down Expand Up @@ -34,6 +35,7 @@ declare namespace State {
readonly scope: Scope.CloseableScope
readonly finalizer: Effect<void>
fiber: RuntimeFiber<void, never> | undefined
expiresAt: number
refCount: number
}
}
Expand Down Expand Up @@ -168,6 +170,7 @@ const acquire = core.fnUntraced(function*<K, A, E>(self: RcMapImpl<K, A, E>, key
scope,
finalizer: undefined as any,
fiber: undefined,
expiresAt: 0,
refCount: 1
}
;(entry as any).finalizer = release(self, key, entry)
Expand All @@ -178,7 +181,7 @@ const acquire = core.fnUntraced(function*<K, A, E>(self: RcMapImpl<K, A, E>, key
})

const release = <K, A, E>(self: RcMapImpl<K, A, E>, key: K, entry: State.Entry<A, E>) =>
core.suspend(() => {
coreEffect.clockWith((clock) => {
entry.refCount--
if (entry.refCount > 0) {
return core.void
Expand All @@ -193,7 +196,10 @@ const release = <K, A, E>(self: RcMapImpl<K, A, E>, key: K, entry: State.Entry<A
return core.scopeClose(entry.scope, core.exitVoid)
}

return coreEffect.sleep(self.idleTimeToLive).pipe(
entry.expiresAt = clock.unsafeCurrentTimeMillis() + Duration.toMillis(self.idleTimeToLive)

return clock.sleep(self.idleTimeToLive).pipe(
core.zipRight(waitUntilExpired(entry, clock)),
core.interruptible,
core.zipRight(core.suspend(() => {
if (self.state._tag === "Open" && entry.refCount === 0) {
Expand All @@ -213,6 +219,16 @@ const release = <K, A, E>(self: RcMapImpl<K, A, E>, key: K, entry: State.Entry<A
)
})

const waitUntilExpired = <A, E>(entry: State.Entry<A, E>, clock: Clock) =>
core.suspend(function loop(): Effect<void> {
const now = clock.unsafeCurrentTimeMillis()
const remaining = entry.expiresAt - now
if (remaining <= 0) {
return core.void
}
return core.flatMap(clock.sleep(Duration.millis(remaining)), loop)
})

/** @internal */
export const keys = <K, A, E>(self: RcMap.RcMap<K, A, E>): Effect<Array<K>> => {
const impl = self as RcMapImpl<K, A, E>
Expand All @@ -239,3 +255,20 @@ export const invalidate: {
if (entry.fiber) yield* core.interruptFiber(entry.fiber)
})
)

/** @internal */
export const touch: {
<K>(key: K): <A, E>(self: RcMap.RcMap<K, A, E>) => Effect<void>
<K, A, E>(self: RcMap.RcMap<K, A, E>, key: K): Effect<void>
} = dual(
2,
<K, A, E>(self_: RcMap.RcMap<K, A, E>, key: K) =>
coreEffect.clockWith((clock) => {
const self = self_ as RcMapImpl<K, A, E>
if (!self.idleTimeToLive || self.state._tag === "Closed") return core.void
const o = MutableHashMap.get(self.state.map, key)
if (o._tag === "None") return core.void
o.value.expiresAt = clock.unsafeCurrentTimeMillis() + Duration.toMillis(self.idleTimeToLive)
return core.void
})
)
31 changes: 31 additions & 0 deletions packages/effect/test/RcMap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,37 @@ describe("RcMap", () => {
assert.deepStrictEqual(released, ["foo", "bar", "baz"])
}))

it.scoped(".touch", () =>
Effect.gen(function*() {
const acquired: Array<string> = []
const released: Array<string> = []
const map = yield* RcMap.make({
lookup: (key: string) =>
Effect.acquireRelease(
Effect.sync(() => {
acquired.push(key)
return key
}),
() => Effect.sync(() => released.push(key))
),
idleTimeToLive: 1000
})

assert.deepStrictEqual(acquired, [])
assert.strictEqual(yield* Effect.scoped(RcMap.get(map, "foo")), "foo")
assert.deepStrictEqual(acquired, ["foo"])
assert.deepStrictEqual(released, [])

yield* TestClock.adjust(500)
assert.deepStrictEqual(released, [])

yield* RcMap.touch(map, "foo")
yield* TestClock.adjust(500)
assert.deepStrictEqual(released, [])
yield* TestClock.adjust(500)
assert.deepStrictEqual(released, ["foo"])
}))

it.scoped("capacity", () =>
Effect.gen(function*() {
const map = yield* RcMap.make({
Expand Down

0 comments on commit a9f2c0e

Please sign in to comment.