{"record":{"id":"3d1cfa6a415bdafa","repo":"linera-io/linera-protocol","slug":"reentrantcall","errorCode":"ReentrantCall","errorMessage":"ExecutionError::ReentrantCall(application_id)","messagePattern":"ExecutionError::ReentrantCall\\(application_id\\)","errorType":"exception","errorClass":"ExecutionError","httpStatus":null,"severity":"error","filePath":"linera-execution/src/runtime.rs","lineNumber":391,"sourceCode":"    /// Ensures the application's ID is also removed from the `active_applications` set.\n    ///\n    /// # Panics\n    ///\n    /// If the call stack is empty.\n    fn pop_application(&mut self) -> ApplicationStatus {\n        let status = self\n            .call_stack\n            .pop()\n            .expect(\"Can't remove application from empty call stack\");\n        assert!(self.active_applications.remove(&status.id));\n        status\n    }\n\n    /// Ensures that a call to `application_id` is not-reentrant.\n    ///\n    /// Returns an error if there already is an entry for `application_id` in the call stack.\n    fn check_for_reentrancy(&self, application_id: ApplicationId) -> Result<(), ExecutionError> {\n        ensure!(\n            !self.active_applications.contains(&application_id),\n            ExecutionError::ReentrantCall(application_id)\n        );\n        Ok(())\n    }\n}\n\nimpl SyncRuntimeInternal<UserContractInstance> {\n    /// Loads a contract instance, initializing it with this runtime if needed.\n    #[instrument(skip_all, fields(application_id = %id))]\n    fn load_contract_instance(\n        &mut self,\n        this: SyncRuntimeHandle<UserContractInstance>,\n        id: ApplicationId,\n    ) -> Result<LoadedApplication<UserContractInstance>, ExecutionError> {\n        match self.loaded_applications.entry(id) {\n            hash_map::Entry::Occupied(entry) => Ok(entry.get().clone()),\n","sourceCodeStart":373,"sourceCodeEnd":409,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-execution/src/runtime.rs#L373-L409","documentation":"check_for_reentrancy keeps the set of applications currently on the call stack (active_applications); if the callee id is already in that set, the call fails with ReentrantCall (runtime.rs:391). Linera forbids any application from appearing twice in one call chain, direct self-calls and longer cycles alike.","triggerScenarios":"try_call_application (via prepare_for_call) where the target application id is already active on the call stack: app A calls B which calls back into A, or A calls itself through the runtime.","commonSituations":"Circular application designs such as a token app calling a marketplace that calls back the token; callback or notification patterns where the callee informs the caller; refactors that introduce a call cycle between cooperating applications.","solutions":["Break the cycle: return a value the caller acts on, instead of calling back into it","Move shared logic into a third application that neither one re-enters","Audit the cross-application call graph for cycles before adding a new edge"],"exampleFix":"// before: B calls back into A (A -> B -> A)\nlet result = runtime.try_call_application(a_id, payload)?;\n\n// after: B returns or records the payload; A continues on its own\nself.emit_outcome(payload);","handlingStrategy":"validation","validationCode":"// Track applications already on the call stack; refuse cycles before calling\nstruct CallGuard { active: HashSet<ApplicationId> }\nimpl CallGuard {\n    fn may_call(&self, callee: ApplicationId) -> Result<(), String> {\n        if self.active.contains(&callee) {\n            return Err(format!(\"call cycle through application {:?}\", callee));\n        }\n        Ok(())\n    }\n}","typeGuard":"fn is_reentrant_call(err: &ExecutionError) -> bool {\n    matches!(err, ExecutionError::ReentrantCall(_))\n}","tryCatchPattern":"match runtime.try_call_application(callee, arg) {\n    Ok(out) => out,\n    Err(ref e) if is_reentrant_call(e) => {\n        // cycle detected: return data instead of calling back\n        Ok(default_response_for(arg))\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Design cross-application flows acyclically: A may call B, B must not call A","Return results to the caller instead of notifying it via a call-back","Audit the application call graph for cycles whenever adding a new cross-app call"],"tags":["reentrancy","cross-application-call","wasm","linera"],"backgroundTag":"reentrant-call","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}