Segfault in csp.dynamic when the trigger basket removes one key and adds another in the same engine cycle
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Ambito
- backend, stream-processing
Direzione di ricerca
Start by reading cpp/csp/engine/DynamicNode.cpp and cpp/csp/engine/BasketInfo.cpp, especially the dynamic-instance add/remove handling and basket index updates described in the report; then inspect cpp/csp/python/PyDynamicNode.cpp for how csp.attach() chooses its basket index. Reproduce with the provided script or the linked variant matrix. Done means same-cycle removal and addition no longer segfault, with the neighboring variants still behaving as expected.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Describe the bug
A node that outputs a csp.DynamicBasket, calls csp.remove_dynamic_key for one key and outputs a different key in the same engine cycle crashes the process (SIGSEGV, fault address 0x8) as soon as that basket is the trigger of a csp.dynamic using csp.attach(). The same trigger consumed by an ordinary node is fine, and the same key traffic spread over separate cycles is fine, so the remove+add combination is what triggers it.
To Reproduce
"""csp segfaults when a csp.dynamic trigger basket removes one key and adds a
different key in the same engine cycle.
uv run python repro.py # or: python repro.py (with csp installed)
Expected: prints the recorded outputs and exits 0.
Observed (csp 0.19.1): SIGSEGV inside PyNodeWrapper_linkFrom while
PyDynamicNode_create instantiates the sub-graph for the newly added key.
"""
from datetime import datetime, timedelta
import csp
from csp import ts
@csp.node
def alternating_key(x: ts[int]) -> csp.DynamicBasket[bool, int]:
"""Key = parity of the tick. Each tick removes the previous key and adds
the other one, so one removal and one addition land in the same cycle."""
with csp.state():
s_prev = None
if csp.ticked(x):
key = bool(x & 1)
if s_prev is not None and s_prev != key:
csp.remove_dynamic_key(s_prev)
s_prev = key
csp.output({key: x})
@csp.graph
def cell(key: bool, v: ts[int]) -> ts[int]:
return v + 1
@csp.node
def first_value(xs: csp.DynamicBasket[bool, int]) -> ts[int]:
if csp.ticked(xs):
out = None
for _, v in xs.tickeditems():
out = v
return out
@csp.graph
def main():
x = csp.count(csp.timer(timedelta(microseconds=1), True))
out = csp.dynamic(alternating_key(x), cell, csp.snapkey(), csp.attach())
csp.add_graph_output("o", first_value(out))
if __name__ == "__main__":
start = datetime(2020, 1, 1)
result = csp.run(main, starttime=start, endtime=start + timedelta(microseconds=10))
print(result["o"])
Standalone repository with this script plus a matrix of neighbouring variants (each run in its own interpreter): https://github.com/hhenson/csp-dynamic-remove-add-segfault
same_cycle_remove_add CRASH (segfault) remove previous key + add new key in one cycle, csp.dynamic consumer
same_cycle_remove_add_older_key CRASH (segfault) the removed key was added two cycles earlier (3-key rotation)
same_cycle_remove_add_no_dynamic ok identical trigger, consumed by a plain node instead of csp.dynamic
separate_cycles_remove_then_add ok removals and additions happen in different cycles
deferred_remove_via_feedback ok the removal is replayed through csp.feedback one engine cycle later
Expected behavior
The run completes and prints the recorded outputs. If removing one key and adding another in the same cycle is meant to be unsupported, a Python exception at csp.remove_dynamic_key/csp.output time rather than a process crash.
Error Message
signal 11 (Segmentation fault: 11), address is 0x8
[bt]: (3) _cspimpl.so csp::python::PyNodeWrapper_linkFrom(csp::python::PyNodeWrapper*, _object*) + 232
[bt]: (4) _cspimpl.so csp::python::PyNodeWrapper_linkFrom(csp::python::PyNodeWrapper*, _object*) + 232
[bt]: (10) _cspimpl.so csp::python::PyDynamicNode_create(...)::$_0::operator()(...) + 384
[bt]: (11) _cspimpl.so csp::DynamicNode::addDynamicInstance(csp::DialectGenericType const&) + 392
[bt]: (12) _cspimpl.so csp::DynamicNode::executeImpl() + 144
[bt]: (13) _cspimpl.so csp::CycleStepTable::executeCycle(csp::Profiler*, bool) + 232
[bt]: (14) _cspimpl.so csp::RootEngine::runSim(csp::DateTime) + 184
[bt]: (15) _cspimpl.so csp::RootEngine::run(csp::DateTime, csp::DateTime) + 176
[bt]: (16) _cspimpl.so csp::python::PyEngine_run(csp::python::PyEngine*, _object*) + 104
The process then aborts from csp's signal handler (exit code 134). The Linux wheel reports the same fault address; its frames are unsymbolized.
Runtime Environment
0.19.1/3.13.15/darwin(macOS 27.0.1 arm64, wheelcp313-cp313-macosx_11_0_arm64)0.19.1/3.14.4/linux(Ubuntu 26.04 x86_64, glibc 2.43, wheelcp314-cp314-manylinux_2_28_x86_64)
Additional context
Reading the 0.19.1 sources, the crash looks like an element-index mismatch between the dynamic node's bookkeeping and the trigger input basket:
DynamicNodekeeps its own key-to-element-index mirror of the trigger basket (m_dynamicKeyMap/m_dynamicElemIdMap), appended inaddDynamicInstanceand swapped-with-last inremoveDynamicInstance.csp.attach()is wired from that mirror:PyDynamicNode_createsets the attached edge'sbasket_idxtopNode->elemId(newkey).- The input basket itself applies both shape events as soon as the trigger node outputs them. A removal swaps the last element into the removed slot and nulls the last slot (
DynamicOutputBasketInfo::removeDynamicKey,DynamicInputBasketInfo::removeDynamicKey). DynamicNode::executeImplprocesses the events in a different order: additions before the inner cycle, removals after it. When the addition is processed, the mirror says the new key was appended at indexsize_before, but the basket has already moved that element into the removed slot and set indexsize_beforetonullptr, so the attached edge points at a null element andPyNodeWrapper_linkFromdereferences it.
Resolving the attach index from the basket's own mapping at instantiation time (the basket's change callback already reports elemId and replaceId), or applying pending removals to the mirror before processing additions, would avoid the stale index.
Workaround that works today: apply removals one engine cycle after additions, for example by replaying the removal through csp.feedback (the deferred_remove_via_feedback variant in the linked repository).
Found while porting a benchmark whose keyed collections replace a few keys every cycle through csp.dynamic.
- Lingua principale
- Python
- Stelle
- 449
- Fork
- 91
- Merge medio
- 13h 21m
- PR unite (30g)
- 6
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Point72/csp
-
Add docs about transitioning from replay to realtime by setting start date in the pastForse già presa @russlan23 l’ha presa 83 giorni fa. Apertapart: documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
RuntimeWarning: non-string key in the __dict__ of class OutputsForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Apertatype: bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
csp::Struct leaks one StructMeta reference per constructionForse già presa @jieya907 l’ha presa 20 giorni fa. Apertagood first issue type: bug
Difficoltà 3/5 1-2 giorni Idoneità per principianti 52/100
I maintainer di solito rispondono entro 1 giorno
-
csp.output does not support dict unpackingForse già presa @aadya940 l’ha presa 91 giorni fa. Apertatype: feature
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
I maintainer di solito rispondono entro 1 giorno
-
type: bug
Difficoltà 3/5 1-2 giorni Idoneità per principianti 45/100
I maintainer di solito rispondono entro 1 giorno
Issue simili
-
follow-up
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
-
query rewriting: prompt says "Language for `query` field: None" when language detection failsAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
modelscope/modelscope#1833 ·
I maintainer di solito rispondono entro 1 giorno
-
ocr_data_to_elements() raises AttributeError when a layout element is a ListForse già presa @Eapp1e l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 79/100
Unstructured-IO/unstructured#4616 ·
I maintainer di solito rispondono entro 1 giorno