Contain provider failures during SHM gap repair
Partially backport only the `get_hist()` exception catch fromwkt/land_flake_update0846cbd4a4, authored by goodboy <goodboy_foss@protonmail.com>. Keep the existing gap fallback. A timed-out IB query raises `NoData` from null-segment repair. Letting it escape cancels sibling history and live quote tasks while `pikerd` can remain alive. Catch `NoData` and `DataUnavailable` locally and stop querying this repair pass. Add a regression using real null detection and a waiting sibling in the same nursery. Both provider failures must return without canceling the sibling. The two cases failed before this catch; the focused history files passed 17 tests with it. Partial-backport-of:0846cbd4a4Prompt-IO: ai/prompt-io/20261106T185628Z_495c0915_215e9b_prompt_io.md (this patch was generated in some part by `codex` (`openai`))
parent
495c091546
commit
51795a4e07
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
created_at: 2026-11-06T18:56:28.621577+00:00
|
||||
generated_by:
|
||||
harness: codex
|
||||
provider: OpenAI
|
||||
git_ref: 495c0915
|
||||
scope: code
|
||||
substantive: true
|
||||
raw_file: 20261106T185628Z_495c0915_215e9b_prompt_io.raw.md
|
||||
---
|
||||
|
||||
## Prompt
|
||||
|
||||
User reported a different-feed overlap failure and asked whether
|
||||
a fix could be extracted without importing broader history work.
|
||||
PDB confirmed MNQ March 2027 one-second history timed out and
|
||||
returned None, producing NoData. After continuing PDB, sibling
|
||||
history and quote tasks terminated while pikerd remained alive.
|
||||
User requested continuing with the narrow backport.
|
||||
|
||||
## Response summary
|
||||
|
||||
Partial backport of the NoData/DataUnavailable catch from
|
||||
0846cbd4a4197e47415689cf5e9d146ba3174c32 into
|
||||
maybe_fill_null_segments(). The existing gap fallback remains in use.
|
||||
A new parametrized regression uses real null detection and verifies
|
||||
that a sibling in the same nursery survives the failed repair.
|
||||
|
||||
Both new cases failed before the catch. The focused history suites
|
||||
passed after it: 17 passed. This does not qualify live overlap or fix
|
||||
IB reset churn or the separate Tractor completion-callback assertion.
|
||||
|
||||
## Files changed
|
||||
|
||||
- `piker/tsp/_history.py`: existing downstream exception catch.
|
||||
- `tests/test_history_backfill.py`: sibling survival regression.
|
||||
|
||||
## Human edits
|
||||
|
||||
User supplied live reproduction, PDB locals and cancellation logs,
|
||||
and explicitly constrained scope to a small history extraction.
|
||||
The production catch is copied from existing downstream commit
|
||||
0846cbd4, not newly designed here. New regression is agent-authored;
|
||||
no direct human source edits observed during this patch. Review and
|
||||
live qualification remain with the user.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
created_at: 2026-11-06T18:56:28.621577+00:00
|
||||
generated_by:
|
||||
harness: codex
|
||||
provider: OpenAI
|
||||
git_ref: 495c0915
|
||||
diff_cmd: git diff HEAD~1..HEAD
|
||||
---
|
||||
|
||||
> `git diff HEAD~1..HEAD -- piker/tsp/_history.py`
|
||||
|
||||
> `git diff HEAD~1..HEAD -- tests/test_history_backfill.py`
|
||||
|
||||
Partial backport of the NoData/DataUnavailable catch from
|
||||
0846cbd4a4197e47415689cf5e9d146ba3174c32 into
|
||||
maybe_fill_null_segments(). The existing gap fallback remains in use.
|
||||
A new parametrized regression uses real null detection and verifies
|
||||
that a sibling in the same nursery survives the failed repair.
|
||||
|
||||
Both new cases failed before the catch. The focused history suites
|
||||
passed after it: 17 passed. This does not qualify live overlap or fix
|
||||
IB reset churn or the separate Tractor completion-callback assertion.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# AI Prompt I/O Log
|
||||
|
||||
This directory records substantive AI-assisted changes, prompts,
|
||||
original harness identity, and unedited outputs. Human contributors
|
||||
remain accountable for code decisions.
|
||||
|
||||
Records follow the [NLNet generative AI policy][policy]. Each
|
||||
structured entry links its immutable `.raw.md` partner. Code output
|
||||
uses Git diff references to avoid duplication.
|
||||
|
||||
[policy]: https://nlnet.nl/foundation/policies/generativeAI/
|
||||
|
|
@ -239,15 +239,26 @@ async def maybe_fill_null_segments(
|
|||
await tractor.pause()
|
||||
break
|
||||
|
||||
(
|
||||
array,
|
||||
next_start_dt,
|
||||
next_end_dt,
|
||||
) = await get_hist(
|
||||
timeframe,
|
||||
start_dt=start_dt,
|
||||
end_dt=end_dt,
|
||||
)
|
||||
try:
|
||||
(
|
||||
array,
|
||||
next_start_dt,
|
||||
next_end_dt,
|
||||
) = await get_hist(
|
||||
timeframe,
|
||||
start_dt=start_dt,
|
||||
end_dt=end_dt,
|
||||
)
|
||||
except (
|
||||
NoData,
|
||||
DataUnavailable,
|
||||
) as exc:
|
||||
log.warning(
|
||||
f'Provider could not repair SHM gap:\n'
|
||||
f'{start_dt} -> {end_dt}\n'
|
||||
f'{exc}\n'
|
||||
)
|
||||
break
|
||||
|
||||
if array.size == 0:
|
||||
log.warning(
|
||||
|
|
|
|||
|
|
@ -7,11 +7,19 @@ from types import SimpleNamespace
|
|||
|
||||
import numpy as np
|
||||
from pendulum import datetime
|
||||
import pytest
|
||||
import trio
|
||||
from trio import TaskStatus
|
||||
|
||||
from piker.brokers import DataUnavailable
|
||||
from piker.brokers import (
|
||||
DataUnavailable,
|
||||
NoData,
|
||||
)
|
||||
from piker.data._source import def_iohlcv_fields
|
||||
from piker.tsp._history import start_backfill
|
||||
from piker.tsp._history import (
|
||||
maybe_fill_null_segments,
|
||||
start_backfill,
|
||||
)
|
||||
|
||||
|
||||
def run_exhausted_backfill(get_hist) -> None:
|
||||
|
|
@ -52,6 +60,82 @@ def test_data_unavailable_completes() -> None:
|
|||
run_exhausted_backfill(get_hist)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('error_type', [
|
||||
NoData,
|
||||
DataUnavailable,
|
||||
])
|
||||
def test_gap_repair_failure_preserves_sibling(
|
||||
error_type: type[Exception],
|
||||
) -> None:
|
||||
'''
|
||||
An unavailable gap query must not cancel sibling feed tasks.
|
||||
|
||||
Use real null detection and a provider that raises on its first
|
||||
query. After repair returns, release a sibling waiting in the
|
||||
same nursery and require its acknowledgement. This checks local
|
||||
failure containment, not Gateway reset or quote reconnection.
|
||||
|
||||
'''
|
||||
frame: np.ndarray = np.zeros(
|
||||
5,
|
||||
dtype=np.dtype(def_iohlcv_fields),
|
||||
)
|
||||
frame['index'] = np.arange(5)
|
||||
frame['time'] = datetime(2026, 11, 6).timestamp() + np.arange(5)
|
||||
frame['time'][1:3] = 0
|
||||
frame['close'][0] = 100
|
||||
calls: list[dict] = []
|
||||
|
||||
async def get_hist(timeframe: float, **kwargs) -> None:
|
||||
assert timeframe == 1
|
||||
assert kwargs['start_dt'] < kwargs['end_dt']
|
||||
calls.append(kwargs)
|
||||
raise error_type('provider cannot repair gap')
|
||||
|
||||
async def main() -> None:
|
||||
repaired: trio.Event = trio.Event()
|
||||
resume: trio.Event = trio.Event()
|
||||
survived: trio.Event = trio.Event()
|
||||
send, receive = trio.open_memory_channel(1)
|
||||
|
||||
async def sibling(
|
||||
task_status: TaskStatus[None] = trio.TASK_STATUS_IGNORED,
|
||||
) -> None:
|
||||
task_status.started()
|
||||
await resume.wait()
|
||||
survived.set()
|
||||
|
||||
async def repair() -> None:
|
||||
await maybe_fill_null_segments(
|
||||
shm=SimpleNamespace(array=frame, _array=frame),
|
||||
timeframe=1,
|
||||
get_hist=get_hist,
|
||||
sampler_stream=send,
|
||||
mkt=SimpleNamespace(fqme='mnq.cme.fake'),
|
||||
backfill_until_dt=datetime(2026, 11, 5),
|
||||
)
|
||||
repaired.set()
|
||||
|
||||
with trio.fail_after(1):
|
||||
async with trio.open_nursery() as nursery:
|
||||
await nursery.start(sibling)
|
||||
nursery.start_soon(repair)
|
||||
await repaired.wait()
|
||||
resume.set()
|
||||
await survived.wait()
|
||||
nursery.cancel_scope.cancel()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert receive.receive_nowait() == {
|
||||
'broadcast_all': {
|
||||
'backfilling': ('mnq.cme.fake', 1),
|
||||
},
|
||||
}
|
||||
assert (frame['time'] > 0).all()
|
||||
|
||||
trio.run(main)
|
||||
|
||||
|
||||
def test_empty_frame_completes() -> None:
|
||||
'''
|
||||
An empty provider frame is checked before endpoint indexing.
|
||||
|
|
|
|||
Loading…
Reference in New Issue