From b6067673bab0c258b5e391c17014fde90d10f20c Mon Sep 17 00:00:00 2001
From: Denis Smirnov <sd@picodata.io>
Date: Thu, 11 Aug 2022 12:11:29 +0700
Subject: [PATCH] feat: never panic while borrowing the cartridge cache

---
 src/api/calculate_bucket_id.rs           | 17 ++++++---
 src/api/calculate_bucket_id_by_dict.rs   | 15 +++++---
 src/api/exec_query.rs                    | 30 ++++++++++++----
 src/api/explain.rs                       | 15 ++++++--
 src/api/helper.rs                        | 46 ++++++++++++++++++------
 src/api/invalidate_cached_schema.rs      | 30 ++++++----------
 src/executor.rs                          |  9 +++--
 src/executor/engine/cartridge/router.rs  |  4 ++-
 src/executor/engine/cartridge/storage.rs | 10 +++++-
 9 files changed, 124 insertions(+), 52 deletions(-)

diff --git a/src/api/calculate_bucket_id.rs b/src/api/calculate_bucket_id.rs
index a0aa22104d..16622b9124 100644
--- a/src/api/calculate_bucket_id.rs
+++ b/src/api/calculate_bucket_id.rs
@@ -34,11 +34,18 @@ pub extern "C" fn calculate_bucket_id(ctx: FunctionCtx, args: FunctionArgs) -> c
         Err(e) => return tarantool::set_error!(TarantoolErrorCode::ProcC, "{:?}", e),
     };
 
-    COORDINATOR_ENGINE.with(|e| {
-        let engine = &mut *e.borrow_mut();
-
-        let result = engine.determine_bucket_id(&[&bucket_str]);
-
+    COORDINATOR_ENGINE.with(|engine| {
+        let runtime = match engine.try_borrow() {
+            Ok(runtime) => runtime,
+            Err(e) => {
+                return tarantool::set_error!(
+                    TarantoolErrorCode::ProcC,
+                    "Failed to borrow the runtime while calculating a bucket id: {:?}",
+                    e
+                )
+            }
+        };
+        let result = runtime.determine_bucket_id(&[&bucket_str]);
         ctx.return_mp(&result).unwrap();
         0
     })
diff --git a/src/api/calculate_bucket_id_by_dict.rs b/src/api/calculate_bucket_id_by_dict.rs
index 3e662a9437..f7d6cdc0ff 100644
--- a/src/api/calculate_bucket_id_by_dict.rs
+++ b/src/api/calculate_bucket_id_by_dict.rs
@@ -61,12 +61,19 @@ pub extern "C" fn calculate_bucket_id_by_dict(ctx: FunctionCtx, args: FunctionAr
     if ret_code != 0 {
         return ret_code;
     }
-    COORDINATOR_ENGINE.with(|e| {
-        let engine = &*e.borrow();
+    COORDINATOR_ENGINE.with(|engine| {
+        let runtime = match engine.try_borrow() {
+            Ok(runtime) => runtime,
+            Err(e) => {
+                return tarantool::set_error!(TarantoolErrorCode::ProcC,
+                    "Failed to borrow the runtime while calculating a bucket id by dictionary: {:?}",
+                    e.to_string());
+            }
+        };
 
-        match engine.extract_sharding_keys(params.space, &params.rec) {
+        match runtime.extract_sharding_keys(params.space, &params.rec) {
             Ok(tuple) => {
-                let bucket_id = engine.determine_bucket_id(&tuple);
+                let bucket_id = runtime.determine_bucket_id(&tuple);
                 ctx.return_mp(&bucket_id).unwrap();
                 0
             }
diff --git a/src/api/exec_query.rs b/src/api/exec_query.rs
index 9bf5ef3a41..c4e17eb8e9 100644
--- a/src/api/exec_query.rs
+++ b/src/api/exec_query.rs
@@ -23,9 +23,18 @@ pub extern "C" fn dispatch_query(ctx: FunctionCtx, args: FunctionArgs) -> c_int
     if ret_code != 0 {
         return ret_code;
     }
-    COORDINATOR_ENGINE.with(|e| {
-        let engine = &*e.borrow();
-        let mut query = match Query::new(engine, &lua_params.pattern, &lua_params.params) {
+    COORDINATOR_ENGINE.with(|engine| {
+        let runtime = match engine.try_borrow() {
+            Ok(runtime) => runtime,
+            Err(e) => {
+                return tarantool::set_error!(
+                    TarantoolErrorCode::ProcC,
+                    "Failed to borrow the runtime while dispatching the query: {}",
+                    e.to_string()
+                );
+            }
+        };
+        let mut query = match Query::new(&*runtime, &lua_params.pattern, &lua_params.params) {
             Ok(q) => q,
             Err(e) => {
                 say(
@@ -104,9 +113,18 @@ pub extern "C" fn execute_query(ctx: FunctionCtx, args: FunctionArgs) -> c_int {
     if ret_code != 0 {
         return ret_code;
     }
-    SEGMENT_ENGINE.with(|e| {
-        let engine = &*e.borrow();
-        match engine.execute(
+    SEGMENT_ENGINE.with(|engine| {
+        let runtime = match engine.try_borrow() {
+            Ok(runtime) => runtime,
+            Err(e) => {
+                return tarantool::set_error!(
+                    TarantoolErrorCode::ProcC,
+                    "Failed to borrow the runtime while executing the query: {}",
+                    e.to_string()
+                );
+            }
+        };
+        match runtime.execute(
             lua_params.pattern.as_str(),
             &lua_params.params,
             lua_params.is_data_modifier,
diff --git a/src/api/explain.rs b/src/api/explain.rs
index dbcecde54b..15d38322d8 100644
--- a/src/api/explain.rs
+++ b/src/api/explain.rs
@@ -39,9 +39,18 @@ pub extern "C" fn explain(ctx: FunctionCtx, args: FunctionArgs) -> c_int {
     if ret_code != 0 {
         return ret_code;
     }
-    COORDINATOR_ENGINE.with(|e| {
-        let engine = &*e.borrow();
-        let query = match Query::new(engine, &lua_params.query, &[]) {
+    COORDINATOR_ENGINE.with(|engine| {
+        let runtime = match engine.try_borrow() {
+            Ok(runtime) => runtime,
+            Err(e) => {
+                return tarantool::set_error!(
+                    TarantoolErrorCode::ProcC,
+                    "Failed to borrow runtime while explaining the query: {}",
+                    e.to_string()
+                );
+            }
+        };
+        let query = match Query::new(&*runtime, &lua_params.query, &[]) {
             Ok(q) => q,
             Err(e) => {
                 say(
diff --git a/src/api/helper.rs b/src/api/helper.rs
index 489efe47eb..f05716f195 100644
--- a/src/api/helper.rs
+++ b/src/api/helper.rs
@@ -4,28 +4,54 @@ use std::os::raw::c_int;
 use std::thread::LocalKey;
 use tarantool::error::TarantoolErrorCode;
 
-pub fn load_config<Runtime>(runtime: &'static LocalKey<RefCell<Runtime>>) -> c_int
+pub fn load_config<Runtime>(engine: &'static LocalKey<RefCell<Runtime>>) -> c_int
 where
     Runtime: Configuration,
 {
     // Tarantool can yield in the middle of a current closure,
     // so we can hold only an immutable reference to the engine.
     let mut config: Option<_> = None;
-    (*runtime).with(|engine| match engine.borrow().get_config() {
-        Ok(conf) => {
-            config = conf;
-            0
-        }
-        Err(e) => {
-            return tarantool::set_error!(TarantoolErrorCode::ProcC, "{}", e.to_string());
+    (*engine).with(|engine| {
+        let runtime = match engine.try_borrow() {
+            Ok(runtime) => runtime,
+            Err(e) => {
+                return tarantool::set_error!(
+                    TarantoolErrorCode::ProcC,
+                    "Failed to borrow the runtime while loading configuration: {}",
+                    e.to_string()
+                );
+            }
+        };
+        match runtime.get_config() {
+            Ok(conf) => {
+                config = conf;
+                0
+            }
+            Err(e) => {
+                return tarantool::set_error!(
+                    TarantoolErrorCode::ProcC,
+                    "Failed to get configuration: {}",
+                    e.to_string()
+                );
+            }
         }
     });
 
     // Tarantool never yields here, so it is possible to hold
     // a mutable reference to the engine.
     if let Some(config) = config {
-        (*runtime).with(|engine| {
-            engine.borrow_mut().update_config(config);
+        (*engine).with(|runtime| {
+            let mut runtime = match runtime.try_borrow_mut() {
+                Ok(runtime) => runtime,
+                Err(e) => {
+                    return tarantool::set_error!(
+                        TarantoolErrorCode::ProcC,
+                        "Failed to borrow the runtime while updating configuration: {}",
+                        e.to_string()
+                    );
+                }
+            };
+            runtime.update_config(config);
             0
         });
     }
diff --git a/src/api/invalidate_cached_schema.rs b/src/api/invalidate_cached_schema.rs
index d1295ba51f..0a2bad396d 100644
--- a/src/api/invalidate_cached_schema.rs
+++ b/src/api/invalidate_cached_schema.rs
@@ -2,41 +2,33 @@ use std::cell::RefCell;
 use std::os::raw::c_int;
 use std::thread::LocalKey;
 use tarantool::error::TarantoolErrorCode;
-use tarantool::log::{say, SayLevel};
 use tarantool::tuple::{FunctionArgs, FunctionCtx};
 
 use crate::api::{COORDINATOR_ENGINE, SEGMENT_ENGINE};
 use crate::executor::engine::Configuration;
 
 fn clear_cached_config<Runtime>(
-    runtime: &'static LocalKey<RefCell<Runtime>>,
+    engine: &'static LocalKey<RefCell<Runtime>>,
     ctx: &FunctionCtx,
     _: FunctionArgs,
 ) -> c_int
 where
     Runtime: Configuration,
 {
-    runtime.with(|s| {
-        if let Ok(mut runtime) = s.try_borrow_mut() {
+    engine.with(|runtime| match runtime.try_borrow_mut() {
+        Ok(mut runtime) => {
             runtime.clear_config();
-        } else {
-            say(
-                SayLevel::Error,
-                file!(),
-                line!().try_into().unwrap_or(0),
-                None,
-                &format!("{:?}", "Failed to borrow runtime: already in use"),
-            );
-            tarantool::set_error!(
+            ctx.return_mp(&true).unwrap();
+            0
+        }
+        Err(e) => {
+            return tarantool::set_error!(
                 TarantoolErrorCode::ProcC,
-                "{}",
-                format!("{:?}", "Failed to borrow runtime: already in use")
+                "Failed to borrow the runtime while clearing cached configuration: {}",
+                e.to_string()
             );
         }
-    });
-
-    ctx.return_mp(&true).unwrap();
-    0
+    })
 }
 
 /// Flush cached configuration in the Rust memory of the coordinator runtime.
diff --git a/src/executor.rs b/src/executor.rs
index 8896eb34ff..bfc38102e6 100644
--- a/src/executor.rs
+++ b/src/executor.rs
@@ -100,13 +100,16 @@ where
         let ir_cache = coordinator.ir_cache();
 
         let mut plan = Plan::new();
-        if let Some(cached_ir) = ir_cache.borrow_mut().get(&key)? {
-            plan = cached_ir.clone();
+        let mut cache = ir_cache.try_borrow_mut().map_err(|e| {
+            QueryPlannerError::CustomError(format!("Failed to create a new query: {:?}", e))
+        })?;
+        if let Some(cached_plan) = cache.get(&key)? {
+            plan = cached_plan.clone();
         }
         if plan.is_empty() {
             let ast = C::ParseTree::new(sql)?;
             plan = ast.resolve_metadata(coordinator.cached_config())?;
-            ir_cache.borrow_mut().put(key, plan.clone())?;
+            cache.put(key, plan.clone())?;
         }
         plan.bind_params(params)?;
         plan.optimize()?;
diff --git a/src/executor/engine/cartridge/router.rs b/src/executor/engine/cartridge/router.rs
index acd5e28f5e..6feda96c12 100644
--- a/src/executor/engine/cartridge/router.rs
+++ b/src/executor/engine/cartridge/router.rs
@@ -147,7 +147,9 @@ impl Coordinator for RouterRuntime {
     type Cache = LRUCache<String, Plan>;
 
     fn clear_ir_cache(&self, capacity: usize) -> Result<(), QueryPlannerError> {
-        *self.ir_cache.borrow_mut() = Self::Cache::new(capacity, None)?;
+        *self.ir_cache.try_borrow_mut().map_err(|e| {
+            QueryPlannerError::CustomError(format!("Failed to clear the cache: {:?}", e))
+        })? = Self::Cache::new(capacity, None)?;
         Ok(())
     }
 
diff --git a/src/executor/engine/cartridge/storage.rs b/src/executor/engine/cartridge/storage.rs
index 08b0c0a6ed..39f9473144 100644
--- a/src/executor/engine/cartridge/storage.rs
+++ b/src/executor/engine/cartridge/storage.rs
@@ -160,7 +160,15 @@ impl StorageRuntime {
                     Option::from("execute"),
                     &format!("Created prepared statement {}", stmt_id),
                 );
-                self.cache.borrow_mut().put(stmt_id, stmt)?;
+                self.cache
+                    .try_borrow_mut()
+                    .map_err(|e| {
+                        QueryPlannerError::CustomError(format!(
+                            "Failed to put prepared statement {:?} into the cache: {:?}",
+                            stmt, e
+                        ))
+                    })?
+                    .put(stmt_id, stmt)?;
                 stmt_id
             }
             Err(e) => {
-- 
GitLab