mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-07 19:20:34 +00:00
* fix(runtime): fence revoked run event producers * fix(runtime): reduce off-mode producer guard locking * docs: keep inherited guidance within size budget * chore(runtime): remove development phase labels * fix(persistence): normalize libpq SSL options for asyncpg connections * docs(runtime): clarify producer fencing behavior in off mode * test(persistence): align migration TLS probes with channel-binding rejection * fix(runtime): authorize fenced Gateway history seeding * fix(scheduler): preserve takeover identity in recovery publication * fix(scheduler): report unconfirmed recovery publication * fix(runtime): preserve history messages before the first human turn * fix(runtime): clarify run creation failure diagnostic --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
98 lines
3.9 KiB
Python
98 lines
3.9 KiB
Python
"""Independent-process actors for history-seed commit/deletion ordering."""
|
|
|
|
import asyncio
|
|
|
|
from sqlalchemy import text
|
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
|
|
from support.producer_process import CONTEXT, ProducerActors, proceed, receive
|
|
|
|
|
|
async def actor(uri, schema, job, identity, pipe):
|
|
from deerflow.runtime.events.producer import HistorySeedAuthority, RunEventProducerRejected, producer_scope
|
|
from deerflow.runtime.events.store.db import DbRunEventStore
|
|
|
|
engine = create_async_engine(uri, connect_args={"server_settings": {"search_path": schema, "statement_timeout": "10000", "lock_timeout": "8000"}})
|
|
sf = async_sessionmaker(engine, expire_on_commit=False)
|
|
try:
|
|
async with engine.connect() as conn:
|
|
pid = await conn.scalar(text("SELECT pg_backend_pid()"))
|
|
pipe.send({"stage": "started", "pid": pid})
|
|
await proceed(pipe)
|
|
events = DbRunEventStore(sf, producer_fencing="strict")
|
|
if job == "delete_locked":
|
|
original = events._invalidate_evidence
|
|
|
|
async def held(*args):
|
|
await original(*args)
|
|
pipe.send({"stage": "revoked_uncommitted"})
|
|
await proceed(pipe)
|
|
|
|
events._invalidate_evidence = held
|
|
if identity.get("delete_run"):
|
|
await events.delete_by_run("thread", "checkpoint-seed-thread-1", user_id="alice")
|
|
else:
|
|
await events.delete_by_thread("thread", user_id="alice")
|
|
pipe.send({"stage": "done", "deleted": True})
|
|
elif job == "metadata_locked":
|
|
from sqlalchemy import select
|
|
|
|
from deerflow.persistence.thread_meta.model import ThreadMetaRow
|
|
|
|
async with sf() as session, session.begin():
|
|
await session.scalar(select(ThreadMetaRow).where(ThreadMetaRow.thread_id == "thread").with_for_update())
|
|
pipe.send({"stage": "metadata_held"})
|
|
await proceed(pipe)
|
|
pipe.send({"stage": "done", "released": True})
|
|
elif job == "admission":
|
|
from deerflow.persistence.run.sql import RunRepository
|
|
from deerflow.runtime import ConflictError, RunManager
|
|
|
|
manager = RunManager(store=RunRepository(sf), worker_id="other-host")
|
|
try:
|
|
await manager.create_or_reject("thread", user_id="alice")
|
|
except ConflictError:
|
|
pipe.send({"stage": "done", "admitted": False})
|
|
else:
|
|
pipe.send({"stage": "done", "admitted": True})
|
|
else:
|
|
authority = HistorySeedAuthority(**identity["authority"])
|
|
original = events._admit_events
|
|
|
|
async def held(session, batch):
|
|
await original(session, batch)
|
|
if job == "seed_locked":
|
|
pipe.send({"stage": "admitted"})
|
|
await proceed(pipe)
|
|
|
|
events._admit_events = held
|
|
try:
|
|
with producer_scope(authority):
|
|
await events.put_batch(identity["events"])
|
|
except RunEventProducerRejected:
|
|
pipe.send({"stage": "done", "written": False})
|
|
else:
|
|
pipe.send({"stage": "done", "written": True})
|
|
except BaseException as exc:
|
|
pipe.send({"stage": "error", "error": f"{type(exc).__name__}: {exc}"})
|
|
raise
|
|
finally:
|
|
await engine.dispose()
|
|
pipe.close()
|
|
|
|
|
|
def process_entry(*args):
|
|
asyncio.run(actor(*args))
|
|
|
|
|
|
class HistorySeedActors(ProducerActors):
|
|
async def start(self, job):
|
|
parent, child = CONTEXT.Pipe()
|
|
process = CONTEXT.Process(target=process_entry, args=(self.uri, self.schema, job, self.identity, child))
|
|
process.start()
|
|
child.close()
|
|
self.processes.append((process, parent))
|
|
message = await receive(parent)
|
|
assert message["stage"] == "started", message
|
|
return parent, message["pid"]
|