Skip to content

Commit 68dc4b8

Browse files
committed
feat: use tracer instead of debug
1 parent 5f0ea5d commit 68dc4b8

6 files changed

Lines changed: 144 additions & 40 deletions

File tree

examples/hello_world.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import asyncio
44
import random
55
import sys
6+
import uuid
67
from pathlib import Path
78

89
import duron
910
from duron.contrib.storage import FileLogStorage
11+
from duron.tracing import LogTracer, use_tracer
1012

1113

1214
@duron.op
@@ -34,7 +36,11 @@ async def greeting_flow(ctx: duron.Context, name: str) -> str:
3436

3537

3638
async def run_workflow(name: str, log_file: Path) -> str:
37-
async with greeting_flow.invoke(FileLogStorage(log_file)) as job:
39+
log = FileLogStorage(log_file)
40+
async with (
41+
use_tracer(LogTracer(str(uuid.uuid4()), log)),
42+
greeting_flow.invoke(log) as job,
43+
):
3844
await job.start(name)
3945
return await job.wait()
4046

src/duron/_core/config.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import os
43
from dataclasses import dataclass
54
from typing import TYPE_CHECKING
65

@@ -13,21 +12,16 @@
1312
@dataclass(slots=True)
1413
class _Config:
1514
codec: Codec
16-
debug: bool
1715

1816

1917
config = _Config(
2018
codec=DefaultCodec(),
21-
debug=os.getenv("DURON_DEBUG", "0").lower() in {"1", "true"},
2219
)
2320

2421

2522
def set_config(
2623
*,
2724
codec: Codec | None = None,
28-
debug: bool | None = None,
2925
) -> None:
3026
if codec is not None:
3127
config.codec = codec
32-
if debug is not None:
33-
config.debug = debug

src/duron/_core/invoke.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
overload,
1515
)
1616

17-
from duron._core.config import config
1817
from duron._core.context import Context
1918
from duron._core.ops import (
2019
Barrier,
@@ -29,6 +28,7 @@
2928
from duron._loop import EventLoop, create_loop
3029
from duron.codec import Codec, JSONValue
3130
from duron.log import derive_id, is_entry, random_id
31+
from duron.tracing import current_tracer
3232
from duron.typing import Unspecified, inspect_function
3333

3434
if TYPE_CHECKING:
@@ -106,7 +106,6 @@ def get_init() -> InitParams:
106106
self._log,
107107
codec,
108108
watchers=self._watchers,
109-
debug=config.debug,
110109
)
111110
await self._run.resume()
112111

@@ -122,7 +121,6 @@ def cb() -> InitParams:
122121
self._log,
123122
self._fn.codec,
124123
watchers=self._watchers,
125-
debug=config.debug,
126124
)
127125
await self._run.resume()
128126

@@ -287,6 +285,7 @@ class _InvokeRun:
287285
__slots__ = (
288286
"_codec",
289287
"_debug",
288+
"_lease",
290289
"_log",
291290
"_loop",
292291
"_now",
@@ -310,15 +309,13 @@ def __init__(
310309
tuple[Callable[[dict[str, JSONValue]], bool], StreamObserver[object]]
311310
]
312311
| None = None,
313-
debug: bool = False,
314312
) -> None:
315313
self._loop = create_loop(asyncio.get_running_loop())
316-
if debug:
317-
self._loop.set_debug(True)
318314
self._task = self._loop.create_task(task)
319315
self._log = log
320316
self._codec = codec
321-
self._running: bytes | None = None
317+
self._running: bool = False
318+
self._lease: bytes | None = None
322319
self._pending_msg: list[Entry] = []
323320
self._pending_task: dict[
324321
str,
@@ -336,11 +333,15 @@ def __init__(
336333
],
337334
] = {}
338335
self._watchers = watchers or []
336+
tracer = current_tracer()
339337
self._debug: dict[str, JSONValue] | None = (
340-
{"run.id": random_id()} if debug else None
338+
{"trace.id": tracer.id()} if tracer else None
341339
)
342340

343341
async def close(self) -> None:
342+
if self._lease:
343+
await self._log.release_lease(self._lease)
344+
self._lease = None
344345
for task, _ in self._tasks.values():
345346
_ = task.cancel()
346347
with contextlib.suppress(asyncio.CancelledError):
@@ -359,6 +360,7 @@ def now(self) -> int:
359360
return self._now
360361

361362
async def resume(self) -> None:
363+
self._lease = await self._log.acquire_lease()
362364
recvd_msgs: set[str] = set()
363365
async for o, entry in self._log.stream(None, live=False):
364366
ts = entry["ts"]
@@ -378,21 +380,17 @@ async def run(self) -> object:
378380
if self._task.done():
379381
return self._task.result()
380382

381-
self._running = await self._log.acquire_lease()
382-
try:
383-
for msg in self._pending_msg:
384-
await self.enqueue_log(msg)
385-
self._pending_msg.clear()
386-
for key, (task_fn, return_type) in self._pending_task.items():
387-
self._tasks[key] = (asyncio.create_task(task_fn()), return_type)
388-
self._pending_task.clear()
389-
390-
while waitset := await self._step():
391-
await waitset.block(self.now())
392-
return self._task.result()
393-
finally:
394-
await self._log.release_lease(self._running)
395-
self._running = None
383+
self._running = True
384+
for msg in self._pending_msg:
385+
await self.enqueue_log(msg)
386+
self._pending_msg.clear()
387+
for key, (task_fn, return_type) in self._pending_task.items():
388+
self._tasks[key] = (asyncio.create_task(task_fn()), return_type)
389+
self._pending_task.clear()
390+
391+
while waitset := await self._step():
392+
await waitset.block(self.now())
393+
return self._task.result()
396394

397395
async def _step(self) -> WaitSet | None:
398396
while True:
@@ -499,6 +497,9 @@ async def handle_message(
499497
async def enqueue_log(self, entry: Entry, *, flush: bool = False) -> None:
500498
if not self._running:
501499
self._pending_msg.append(entry)
500+
elif self._lease is None:
501+
# closed
502+
return
502503
else:
503504
if self._debug:
504505
if "debug" in entry:
@@ -510,9 +511,9 @@ async def enqueue_log(self, entry: Entry, *, flush: bool = False) -> None:
510511
entry["debug"] = self._debug
511512
else:
512513
_ = entry.pop("debug", None)
513-
offset = await self._log.append(self._running, entry)
514+
offset = await self._log.append(self._lease, entry)
514515
if flush:
515-
await self._log.flush(self._running)
516+
await self._log.flush(self._lease)
516517
await self.handle_message(offset, entry)
517518

518519
async def enqueue_op(self, id_: str, fut: OpFuture[object]) -> None:

src/duron/_decorator/fn.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ def __call__(
5050
) -> Coroutine[Any, Any, _T_co]:
5151
return self.fn(ctx, *args, **kwargs)
5252

53-
def invoke(self, log: LogStorage) -> AsyncContextManager[Invoke[_P, _T_co]]:
53+
def invoke(
54+
self,
55+
log: LogStorage,
56+
/,
57+
) -> AsyncContextManager[Invoke[_P, _T_co]]:
5458
return Invoke[_P, _T_co].invoke(self, log)
5559

5660

src/duron/_loop.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ class EventLoop(asyncio.AbstractEventLoop):
8383
__slots__: tuple[str, ...] = (
8484
"_closed",
8585
"_ctx",
86-
"_debug",
8786
"_event",
8887
"_exc_handler",
8988
"_host",
@@ -95,7 +94,6 @@ class EventLoop(asyncio.AbstractEventLoop):
9594

9695
def __init__(self, host: asyncio.AbstractEventLoop) -> None:
9796
self._ready: deque[asyncio.Handle] = deque()
98-
self._debug: bool = False
9997
self._host: asyncio.AbstractEventLoop = host
10098
self._exc_handler: (
10199
Callable[[asyncio.AbstractEventLoop, dict[str, object]], object] | None
@@ -317,11 +315,7 @@ def close(self) -> None:
317315

318316
@override
319317
def get_debug(self) -> bool:
320-
return self._debug
321-
322-
@override
323-
def set_debug(self, enabled: bool) -> None:
324-
self._debug = enabled
318+
return False
325319

326320
@override
327321
def default_exception_handler(self, context: dict[str, object]) -> None:

src/duron/tracing/__init__.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from __future__ import annotations
2+
3+
from contextvars import ContextVar
4+
from typing import TYPE_CHECKING, Final, Protocol, final
5+
6+
if TYPE_CHECKING:
7+
from contextvars import Token
8+
from types import TracebackType
9+
10+
from duron.log import LogStorage
11+
12+
_current_tracer: ContextVar[Tracer | None] = ContextVar("duron_tracer", default=None)
13+
14+
15+
class Tracer(Protocol):
16+
def id(self) -> str: ...
17+
18+
def enabled(self) -> bool: ...
19+
def __bool__(self) -> bool: ...
20+
21+
22+
@final
23+
class _NoopTracer:
24+
__slots__: tuple[str, ...] = ()
25+
26+
@staticmethod
27+
def id() -> str:
28+
return ""
29+
30+
@staticmethod
31+
def enabled() -> bool:
32+
return False
33+
34+
@staticmethod
35+
def __bool__() -> bool:
36+
return False
37+
38+
39+
_Noop: Final = _NoopTracer()
40+
41+
42+
class LogTracer:
43+
__slots__: tuple[str, ...] = ("_id", "_log")
44+
45+
def __init__(
46+
self,
47+
id_: str,
48+
log: LogStorage,
49+
/,
50+
) -> None:
51+
self._log: LogStorage = log
52+
self._id: str = id_
53+
54+
def id(self) -> str:
55+
return self._id
56+
57+
@staticmethod
58+
def enabled() -> bool:
59+
return True
60+
61+
@staticmethod
62+
def __bool__() -> bool:
63+
return True
64+
65+
66+
def current_tracer() -> Tracer:
67+
return _current_tracer.get() or _Noop
68+
69+
70+
class _TracerGuard:
71+
__slots__ = ("_token", "_value")
72+
73+
def __init__(self, value: Tracer | None) -> None:
74+
self._value = value
75+
self._token: Token[Tracer | None] | None = None
76+
77+
def __enter__(self) -> None:
78+
self._token = _current_tracer.set(self._value)
79+
80+
def __exit__(
81+
self,
82+
_exc_type: type[BaseException] | None,
83+
_exc_value: BaseException | None,
84+
_traceback: TracebackType | None,
85+
) -> None:
86+
if self._token is not None:
87+
_current_tracer.reset(self._token)
88+
self._token = None
89+
90+
async def __aenter__(self) -> None:
91+
self._token = _current_tracer.set(self._value)
92+
93+
async def __aexit__(
94+
self,
95+
_exc_type: type[BaseException] | None,
96+
_exc_value: BaseException | None,
97+
_traceback: TracebackType | None,
98+
) -> None:
99+
if self._token is not None:
100+
_current_tracer.reset(self._token)
101+
self._token = None
102+
103+
104+
def use_tracer(tracer: Tracer | None) -> _TracerGuard:
105+
return _TracerGuard(tracer)

0 commit comments

Comments
 (0)