Improve Redis fail-fast behavior and resilient store initialization

This commit is contained in:
Jacob Schmidt 2026-03-06 21:45:17 -06:00
parent 6c8490f299
commit a373943eca
21 changed files with 331 additions and 126 deletions

View File

@ -52,6 +52,7 @@ PREP_RECOMPILE_END;
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
GVAR(ActorStore) call ["snapshot", [_uid]];
private _finalData = GVAR(ActorStore) call ["save", [GVAR(Registry), "actor:update", _uid]];
private _player = [_uid] call EFUNC(common,getPlayer);

View File

@ -114,15 +114,22 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
["init", compileFinal {
params [["_uid", "", [""]]];
private _cached = GVAR(Registry) getOrDefault [_uid, nil];
if !(isNil { _cached }) exitWith { _cached };
private _player = [_uid] call EFUNC(common,getPlayer);
private _cached = GVAR(Registry) getOrDefault [_uid, nil];
if !(isNil { _cached }) exitWith { [CRPC(actor,responseInitActor), [_cached], _player] call CFUNC(targetEvent); _cached };
["actor:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to check if actor %1 exists!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to check if actor %1 exists! Using fallback actor.", _uid]] call EFUNC(common,log);
private _fallbackActor = GVAR(ActorModel) call ["fromPlayer", [_player]];
_fallbackActor set ["uid", _uid];
_fallbackActor = GVAR(ActorModel) call ["migrate", [_fallbackActor]];
GVAR(Registry) set [_uid, _fallbackActor];
[CRPC(actor,responseInitActor), [_fallbackActor], _player] call CFUNC(targetEvent);
_fallbackActor
};
private _finalActor = createHashMap;
@ -137,8 +144,13 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
private _json = _self call ["toJSON", [_finalActor]];
["actor:create", [_uid, _json]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to create actor %1!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to create actor %1! Using fallback actor.", _uid]] call EFUNC(common,log);
_finalActor = GVAR(ActorModel) call ["migrate", [_finalActor]];
GVAR(Registry) set [_uid, _finalActor];
[CRPC(actor,responseInitActor), [_finalActor], _player] call CFUNC(targetEvent);
_finalActor
};
["INFO", format ["Created new actor for %1", _uid]] call EFUNC(common,log);
@ -148,6 +160,36 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
GVAR(Registry) set [_uid, _finalActor];
[CRPC(actor,responseInitActor), [_finalActor], _player] call CFUNC(targetEvent);
_finalActor
}],
["snapshot", compileFinal {
params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer);
private _existing = GVAR(Registry) getOrDefault [_uid, createHashMap];
private _finalActor = +_existing;
if (_finalActor isEqualTo createHashMap) then {
_finalActor = GVAR(ActorModel) call ["defaults", []];
_finalActor set ["uid", _uid];
};
if (_player isNotEqualTo objNull) then {
_finalActor set ["uid", _uid];
_finalActor set ["name", name _player];
_finalActor set ["position", getPosASL _player];
_finalActor set ["direction", getDir _player];
_finalActor set ["stance", stance _player];
_finalActor set ["rank", rank _player];
_finalActor set ["state", lifeState _player];
_finalActor set ["loadout", getUnitLoadout _player];
} else {
["WARNING", format ["No player object found for %1 during actor snapshot, using cached values.", _uid]] call EFUNC(common,log);
};
_finalActor = GVAR(ActorModel) call ["migrate", [_finalActor]];
GVAR(Registry) set [_uid, _finalActor];
_finalActor
}]
];

View File

@ -102,15 +102,24 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
["init", compileFinal {
params [["_uid", "", [""]]];
private _cached = GVAR(Registry) getOrDefault [_uid, nil];
if !(isNil { _cached }) exitWith { _cached };
private _player = [_uid] call EFUNC(common,getPlayer);
private _cached = GVAR(Registry) getOrDefault [_uid, nil];
if !(isNil { _cached }) exitWith { [CRPC(bank,responseInitBank), [_cached], _player] call CFUNC(targetEvent); _cached };
["bank:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to check if bank account %1 exists!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to check if bank account %1 exists! Using fallback account.", _uid]] call EFUNC(common,log);
private _fallbackAccount = GVAR(BankModel) call ["fromPlayer", [_player]];
_fallbackAccount set ["uid", _uid];
private _regEntry = createHashMapFromArray [["uid", _uid], ["name", (name _player)]];
GVAR(IndexRegistry) set [_uid, _regEntry];
GVAR(Registry) set [_uid, _fallbackAccount];
[CRPC(bank,responseInitBank), [_fallbackAccount], _player] call CFUNC(targetEvent);
_fallbackAccount
};
private _finalAccount = createHashMap;
@ -125,8 +134,15 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
private _json = _self call ["toJSON", [_finalAccount]];
["bank:create", [_uid, _json]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to create bank account %1!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to create bank account %1! Using fallback account.", _uid]] call EFUNC(common,log);
private _regEntry = createHashMapFromArray [["uid", _uid], ["name", (name _player)]];
GVAR(IndexRegistry) set [_uid, _regEntry];
GVAR(Registry) set [_uid, _finalAccount];
[CRPC(bank,responseInitBank), [_finalAccount], _player] call CFUNC(targetEvent);
_finalAccount
};
["INFO", format ["Created new bank account for %1", _uid]] call EFUNC(common,log);

View File

@ -25,6 +25,27 @@
params [["_function", "", [""]], ["_arguments", [], [[]]]];
["INFO", format ["Calling function: %1", _function], nil, nil] call EFUNC(common,log);
private _functionLower = toLower _function;
private _requiresRedis = !(_functionLower in ["status", "version"])
&& (_functionLower find "icom:" == 0)
&& (_functionLower find "terrain:" == 0);
if (_requiresRedis) then {
("forge_server" callExtension ["status", []]) params ["_redisStatus", "_statusExtCode", "_statusArmaCode"];
private _statusSuccess = (_statusExtCode == 0) && (_statusArmaCode == 0 || _statusArmaCode == 301);
if (!_statusSuccess) exitWith {
["WARNING", "Unable to determine Redis status before extension call", nil, nil] call EFUNC(common,log);
["Error: Redis status check failed", false]
};
if (_redisStatus != "connected") exitWith {
["WARNING", format ["Blocked extension call '%1' because Redis status is '%2'", _function, _redisStatus], nil, nil] call EFUNC(common,log);
[format ["Error: Redis is %1", _redisStatus], false]
};
};
("forge_server" callExtension [_function, _arguments]) params ["_result", "_extCode", "_armaCode"];
private _success = true;

View File

@ -32,13 +32,19 @@ GVAR(GarageBaseStore) = compileFinal createHashMapFromArray [
["init", compileFinal {
params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer);
private _cached = GVAR(Registry) getOrDefault [_uid, nil];
if !(isNil { _cached }) exitWith { _cached };
if !(isNil { _cached }) exitWith { [CRPC(garage,responseInitGarage), [_cached], _player] call CFUNC(targetEvent); _cached };
["garage:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to check if garage %1 exists!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to check if garage %1 exists! Using fallback garage.", _uid]] call EFUNC(common,log);
private _fallbackGarage = createHashMap;
GVAR(Registry) set [_uid, _fallbackGarage];
[CRPC(garage,responseInitGarage), [_fallbackGarage], _player] call CFUNC(targetEvent);
_fallbackGarage
};
private _finalGarage = createHashMap;
@ -49,15 +55,17 @@ GVAR(GarageBaseStore) = compileFinal createHashMapFromArray [
} else {
["garage:create", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to create garage for %1!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to create garage for %1! Using fallback garage.", _uid]] call EFUNC(common,log);
GVAR(Registry) set [_uid, _finalGarage];
[CRPC(garage,responseInitGarage), [_finalGarage], _player] call CFUNC(targetEvent);
_finalGarage
};
["INFO", format ["Created new garage for %1", _uid]] call EFUNC(common,log);
};
private _player = [_uid] call EFUNC(common,getPlayer);
GVAR(Registry) set [_uid, _finalGarage];
[CRPC(garage,responseInitGarage), [_finalGarage], _player] call CFUNC(targetEvent);

View File

@ -48,13 +48,22 @@ GVAR(VGBaseStore) = compileFinal createHashMapFromArray [
["init", compileFinal {
params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer);
private _cached = GVAR(VGRegistry) getOrDefault [_uid, nil];
if !(isNil { _cached }) exitWith { _cached };
if !(isNil { _cached }) exitWith {
[CRPC(garage,responseInitVG), [_cached], _player] call CFUNC(targetEvent);
_cached
};
["owned:garage:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to check if virtual garage %1 exists!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to check if virtual garage %1 exists! Using fallback virtual garage.", _uid]] call EFUNC(common,log);
private _fallbackVGarage = GVAR(VGarageModel) call ["defaults", []];
GVAR(VGRegistry) set [_uid, _fallbackVGarage];
[CRPC(garage,responseInitVG), [_fallbackVGarage], _player] call CFUNC(targetEvent);
_fallbackVGarage
};
private _finalVGarage = createHashMap;
@ -67,15 +76,17 @@ GVAR(VGBaseStore) = compileFinal createHashMapFromArray [
["owned:garage:create", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to create virtual garage for %1!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to create virtual garage for %1! Using fallback virtual garage.", _uid]] call EFUNC(common,log);
GVAR(VGRegistry) set [_uid, _finalVGarage];
[CRPC(garage,responseInitVG), [_finalVGarage], _player] call CFUNC(targetEvent);
_finalVGarage
};
["INFO", format ["Created new virtual garage for %1", _uid]] call EFUNC(common,log);
};
private _player = [_uid] call EFUNC(common,getPlayer);
GVAR(VGRegistry) set [_uid, _finalVGarage];
[CRPC(garage,responseInitVG), [_finalVGarage], _player] call CFUNC(targetEvent);

View File

@ -32,13 +32,19 @@ GVAR(LockerBaseStore) = compileFinal createHashMapFromArray [
["init", compileFinal {
params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer);
private _cached = GVAR(Registry) getOrDefault [_uid, nil];
if !(isNil { _cached }) exitWith { _cached };
if !(isNil { _cached }) exitWith { [CRPC(locker,responseInitLocker), [_cached], _player] call CFUNC(targetEvent); _cached };
["locker:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to check if locker %1 exists!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to check if locker %1 exists! Using fallback locker.", _uid]] call EFUNC(common,log);
private _fallbackLocker = createHashMap;
GVAR(Registry) set [_uid, _fallbackLocker];
[CRPC(locker,responseInitLocker), [_fallbackLocker], _player] call CFUNC(targetEvent);
_fallbackLocker
};
private _finalLocker = createHashMap;
@ -49,15 +55,17 @@ GVAR(LockerBaseStore) = compileFinal createHashMapFromArray [
} else {
["locker:create", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to create locker for %1!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to create locker for %1! Using fallback locker.", _uid]] call EFUNC(common,log);
GVAR(Registry) set [_uid, _finalLocker];
[CRPC(locker,responseInitLocker), [_finalLocker], _player] call CFUNC(targetEvent);
_finalLocker
};
["INFO", format ["Created new locker for %1", _uid]] call EFUNC(common,log);
};
private _player = [_uid] call EFUNC(common,getPlayer);
GVAR(Registry) set [_uid, _finalLocker];
[CRPC(locker,responseInitLocker), [_finalLocker], _player] call CFUNC(targetEvent);

View File

@ -46,13 +46,22 @@ GVAR(VABaseStore) = compileFinal createHashMapFromArray [
["init", compileFinal {
params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer);
private _cached = GVAR(VARegistry) getOrDefault [_uid, nil];
if !(isNil { _cached }) exitWith { _cached };
if !(isNil { _cached }) exitWith {
[CRPC(locker,responseInitVA), [_cached], _player] call CFUNC(targetEvent);
_cached
};
["owned:locker:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to check if virtual arsenal %1 exists!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to check if virtual arsenal %1 exists! Using fallback virtual arsenal.", _uid]] call EFUNC(common,log);
private _fallbackVArsenal = GVAR(VArsenalModel) call ["defaults", []];
GVAR(VARegistry) set [_uid, _fallbackVArsenal];
[CRPC(locker,responseInitVA), [_fallbackVArsenal], _player] call CFUNC(targetEvent);
_fallbackVArsenal
};
private _finalVArsenal = createHashMap;
@ -65,15 +74,17 @@ GVAR(VABaseStore) = compileFinal createHashMapFromArray [
["owned:locker:create", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to create virtual arsenal for %1!", _uid]] call EFUNC(common,log);
createHashMap
["ERROR", format ["Failed to create virtual arsenal for %1! Using fallback virtual arsenal.", _uid]] call EFUNC(common,log);
GVAR(VARegistry) set [_uid, _finalVArsenal];
[CRPC(locker,responseInitVA), [_finalVArsenal], _player] call CFUNC(targetEvent);
_finalVArsenal
};
["INFO", format ["Created new virtual arsenal for %1", _uid]] call EFUNC(common,log);
};
private _player = [_uid] call EFUNC(common,getPlayer);
GVAR(VARegistry) set [_uid, _finalVArsenal];
[CRPC(locker,responseInitVA), [_finalVArsenal], _player] call CFUNC(targetEvent);

View File

@ -7,12 +7,11 @@ PREP_RECOMPILE_END;
// private _category = [QUOTE(MOD_NAME), LLSTRING(displayName)];
[QGVAR(requestInitOrg), {
params [["_uid", "", [""]], ["_org", createHashMap, [createHashMap]]];
params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid UID!" };
if (_org isEqualTo createHashMap) exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid Org data!" };
GVAR(OrgStore) call ["init", [_uid, _org]];
GVAR(OrgStore) call ["init", [_uid]];
}] call CFUNC(addEventHandler);
[QGVAR(requestGetOrg), {

View File

@ -81,7 +81,18 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
["org:exists", ["default"]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", "Failed to check for default org!"] call EFUNC(common,log);
createHashMap;
private _fallbackDefaultOrg = createHashMapFromArray [
["id", "default"],
["owner", "server"],
["name", "Forge Dynamics"],
["funds", 200000],
["reputation", 0],
["members", createHashMap]
];
GVAR(Registry) set ["default", _fallbackDefaultOrg];
_fallbackDefaultOrg
};
private _finalOrg = createHashMap;
@ -104,23 +115,39 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
["init", compileFinal {
params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer);
private _actor = EGVAR(actor,Registry) get _uid;
private _orgID = _actor get "organization";
if (_orgID isEqualTo "") then { _orgID = "default" };
private _cached = GVAR(Registry) getOrDefault [_orgID, nil];
if !(isNil { _cached }) exitWith { _cached };
if !(isNil { _cached }) exitWith { [CRPC(org,responseInitOrg), [_cached], _player] call CFUNC(targetEvent); _cached };
["org:exists", [_orgID]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
if !(_isSuccess) exitWith {
["ERROR", format ["Failed to check for org %1!", _orgID]] call EFUNC(common,log);
createHashMap;
["ERROR", format ["Failed to check for org %1! Using fallback org.", _orgID]] call EFUNC(common,log);
private _fallbackOrg = GVAR(Registry) getOrDefault ["default", createHashMapFromArray [
["id", "default"],
["owner", "server"],
["name", "Forge Dynamics"],
["funds", 200000],
["reputation", 0],
["members", createHashMap]
]];
private _entry = createHashMapFromArray [["orgID", _orgID]];
GVAR(IndexRegistry) set [_uid, _entry];
GVAR(Registry) set [_orgID, _fallbackOrg, true];
[CRPC(org,responseInitOrg), [_fallbackOrg], _player] call CFUNC(targetEvent);
_fallbackOrg;
};
private _finalOrg = createHashMap;
private _finalMembers = createHashMap;
// private _finalAssets = createHashMap;
private _player = [_uid] call EFUNC(common,getPlayer);
if (_result == "true") then {
_finalOrg = _self call ["fetch", ["org:get", _orgID]];

View File

@ -16,6 +16,9 @@ db = 0 # Redis database number (0-15)
max_connections = 10 # Maximum number of connections in pool
min_connections = 2 # Minimum number of idle connections
idle_timeout = 60 # Idle connection timeout in seconds
connect_timeout_ms = 2000 # Pool connect timeout in milliseconds
pool_get_timeout_ms = 2000 # Pool checkout timeout in milliseconds
command_timeout_ms = 2000 # Redis command timeout in milliseconds
# Example configurations for different environments:
@ -33,3 +36,6 @@ idle_timeout = 60 # Idle connection timeout in seconds
# max_connections = 20
# min_connections = 5
# idle_timeout = 30
# connect_timeout_ms = 5000
# pool_get_timeout_ms = 5000
# command_timeout_ms = 5000

View File

@ -16,6 +16,9 @@ db = 0 # Redis database number (0-15)
max_connections = 10 # Maximum number of connections in pool
min_connections = 2 # Minimum number of idle connections
idle_timeout = 60 # Idle connection timeout in seconds
connect_timeout_ms = 2000 # Pool connect timeout in milliseconds
pool_get_timeout_ms = 2000 # Pool checkout timeout in milliseconds
command_timeout_ms = 2000 # Redis command timeout in milliseconds
# Example configurations for different environments:
@ -33,3 +36,6 @@ idle_timeout = 60 # Idle connection timeout in seconds
# max_connections = 20
# min_connections = 5
# idle_timeout = 30
# connect_timeout_ms = 5000
# pool_get_timeout_ms = 5000
# command_timeout_ms = 5000

View File

@ -241,13 +241,12 @@ pub fn actor_exists(call_context: CallContext, key: String) -> String {
match ACTOR_SERVICE.actor_exists(resolved_uid.clone()) {
Ok(exists) => {
let result = if exists { "true" } else { "false" };
log(
"actor",
"DEBUG",
&format!("Actor '{}' exists: {}", resolved_uid, result),
&format!("Actor '{}' exists: {}", resolved_uid, exists),
);
result.to_string()
exists.to_string()
}
Err(e) => {
log(

View File

@ -222,11 +222,7 @@ pub fn bank_exists(call_context: CallContext, key: String) -> String {
let resolved_uid = match resolve_uid(&key, &call_context) {
Some(uid) => {
log(
"bank",
"DEBUG",
&format!("Resolved UID for existence check: {}", uid),
);
log("bank", "DEBUG", &format!("Resolved UID: {}", uid));
uid
}
None => {
@ -241,13 +237,12 @@ pub fn bank_exists(call_context: CallContext, key: String) -> String {
match BANK_SERVICE.bank_exists(resolved_uid.clone()) {
Ok(exists) => {
let result = if exists { "true" } else { "false" };
log(
"bank",
"DEBUG",
&format!("Bank '{}' exists: {}", resolved_uid, result),
&format!("Bank '{}' exists: {}", resolved_uid, exists),
);
result.to_string()
exists.to_string()
}
Err(e) => {
log(

View File

@ -518,9 +518,12 @@ pub fn garage_exists(call_context: CallContext, key: String) -> String {
uid
}
None => {
let error_msg = format!("Error: Failed to resolve UID for key: {}", key);
log("garage", "ERROR", &error_msg);
return error_msg;
log(
"garage",
"ERROR",
&format!("Failed to resolve UID for key: {}", key),
);
return "false".to_string();
}
};
@ -529,18 +532,17 @@ pub fn garage_exists(call_context: CallContext, key: String) -> String {
log(
"garage",
"DEBUG",
&format!("Garage exists for '{}': {}", resolved_uid, exists),
&format!("Garage '{}' exists: {}", resolved_uid, exists),
);
exists.to_string()
}
Err(e) => {
let error_msg = format!("Error: {}", e);
log(
"garage",
"ERROR",
&format!("Failed to check garage existence '{}': {}", resolved_uid, e),
&format!("Failed to check if garage '{}' exists: {}", resolved_uid, e),
);
error_msg
"false".to_string()
}
}
}

View File

@ -473,9 +473,12 @@ pub fn locker_exists(call_context: CallContext, key: String) -> String {
uid
}
None => {
let error_msg = format!("Error: Failed to resolve UID for key: {}", key);
log("locker", "ERROR", &error_msg);
return error_msg;
log(
"locker",
"ERROR",
&format!("Failed to resolve UID for key: {}", key),
);
return "false".to_string();
}
};
@ -483,13 +486,12 @@ pub fn locker_exists(call_context: CallContext, key: String) -> String {
Ok(exists) => {
log(
"locker",
"INFO",
&format!("Locker exists for: {}", resolved_uid),
"DEBUG",
&format!("Locker '{}' exists: {}", resolved_uid, exists),
);
exists.to_string()
}
Err(e) => {
let error_msg = format!("Error: {}", e);
log(
"locker",
"ERROR",
@ -498,7 +500,7 @@ pub fn locker_exists(call_context: CallContext, key: String) -> String {
resolved_uid, e
),
);
error_msg
"false".to_string()
}
}
}

View File

@ -1,6 +1,7 @@
use super::config::RedisConfig;
use bb8_redis::{RedisConnectionManager, bb8};
use std::error::Error;
use std::time::Duration;
/// Redis connection pool type alias.
pub type RedisClient = bb8::Pool<RedisConnectionManager>;
@ -33,10 +34,14 @@ pub async fn create_redis_pool(
// Configure idle connection timeout if specified
// This prevents keeping stale connections that might be closed by the server
if let Some(idle_timeout) = config.idle_timeout {
use std::time::Duration;
pool_builder = pool_builder.idle_timeout(Some(Duration::from_secs(idle_timeout)));
}
// Bound connection acquisition from the pool so game thread calls fail fast
if let Some(connect_timeout_ms) = config.connect_timeout_ms {
pool_builder = pool_builder.connection_timeout(Duration::from_millis(connect_timeout_ms));
}
// Build the final connection pool with all configured parameters
let pool = pool_builder.build(manager).await?;
Ok(pool)

View File

@ -3,9 +3,12 @@
use serde::Deserialize;
use std::fs;
use std::path::PathBuf;
use std::sync::OnceLock;
use crate::log::log;
static CONFIG_CACHE: OnceLock<Config> = OnceLock::new();
/// Main configuration structure for the entire application.
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
@ -42,6 +45,12 @@ pub struct RedisConfig {
pub min_connections: Option<usize>,
/// Idle connection timeout in seconds
pub idle_timeout: Option<u64>,
/// Maximum time to wait for pool connection checkout in milliseconds
pub pool_get_timeout_ms: Option<u64>,
/// Maximum time to wait for individual Redis command execution in milliseconds
pub command_timeout_ms: Option<u64>,
/// Maximum time to wait for pool connection establishment in milliseconds
pub connect_timeout_ms: Option<u64>,
}
impl Default for RedisConfig {
@ -56,6 +65,9 @@ impl Default for RedisConfig {
max_connections: Some(10),
min_connections: Some(2),
idle_timeout: Some(60),
pool_get_timeout_ms: Some(2000),
command_timeout_ms: Some(2000),
connect_timeout_ms: Some(2000),
}
}
}
@ -93,30 +105,34 @@ impl RedisConfig {
/// Loads configuration from the `config.toml` file with graceful fallback to defaults.
pub fn load() -> Config {
let config_path = std::env::current_exe()
.ok()
.and_then(|exe| {
exe.parent()
.map(|dir| dir.join("@forge_server").join("config.toml"))
})
.filter(|p| p.exists())
.unwrap_or_else(|| PathBuf::from("@forge_server/config.toml"));
CONFIG_CACHE
.get_or_init(|| {
let config_path = std::env::current_exe()
.ok()
.and_then(|exe| {
exe.parent()
.map(|dir| dir.join("@forge_server").join("config.toml"))
})
.filter(|p| p.exists())
.unwrap_or_else(|| PathBuf::from("@forge_server/config.toml"));
match fs::read_to_string(&config_path) {
Ok(contents) => {
log("main", "INFO", &format!("Config file found! Loading..."));
match toml::from_str::<Config>(&contents) {
Ok(config) => config,
Err(_) => Config::default(),
match fs::read_to_string(&config_path) {
Ok(contents) => {
log("main", "INFO", &format!("Config file found! Loading..."));
match toml::from_str::<Config>(&contents) {
Ok(config) => config,
Err(_) => Config::default(),
}
}
Err(_) => {
log(
"main",
"INFO",
&format!("Config file not found. Using default configuration."),
);
Config::default()
}
}
}
Err(_) => {
log(
"main",
"INFO",
&format!("Config file not found. Using default configuration."),
);
Config::default()
}
}
})
.clone()
}

View File

@ -4,32 +4,54 @@
#[macro_export]
macro_rules! redis_operation {
($conn:ident => $operation:block) => {{
use tokio::time::{Duration, timeout};
use $crate::redis;
use $crate::{CONNECTION_STATE, ConnectionState, REDIS_POOL, RUNTIME};
let timeout_config = redis::config::load().redis;
let pool_get_timeout =
Duration::from_millis(timeout_config.pool_get_timeout_ms.unwrap_or(2000));
let command_timeout =
Duration::from_millis(timeout_config.command_timeout_ms.unwrap_or(2000));
let init_timeout = Duration::from_millis(timeout_config.connect_timeout_ms.unwrap_or(2000));
// Get the Redis connection pool (initialized at startup)
let pool = match REDIS_POOL.get() {
Some(pool) => pool,
None => {
if *CONNECTION_STATE.read().unwrap() == ConnectionState::Failed {
return "Error: Redis connection unavailable".to_string();
}
// Attempt lazy initialization if not already initialized
let rt = &RUNTIME;
let init_result = rt.block_on(async move {
let cfg = redis::config::load();
match redis::client::create_redis_pool(&cfg.redis).await {
Ok(pool) => {
match timeout(init_timeout, redis::client::create_redis_pool(&cfg.redis)).await
{
Ok(Ok(pool)) => {
let _ = REDIS_POOL.set(pool);
Ok(())
}
Err(_e) => {
Ok(Err(_e)) => {
let default_cfg = redis::RedisConfig::default();
match redis::client::create_redis_pool(&default_cfg).await {
Ok(pool) => {
match timeout(
init_timeout,
redis::client::create_redis_pool(&default_cfg),
)
.await
{
Ok(Ok(pool)) => {
let _ = REDIS_POOL.set(pool);
Ok(())
}
Err(e) => Err(format!("{}", e)),
Ok(Err(e)) => Err(format!("{}", e)),
Err(_) => {
Err("Redis fallback initialization timed out".to_string())
}
}
}
Err(_) => Err("Redis initialization timed out".to_string()),
}
});
@ -53,13 +75,17 @@ macro_rules! redis_operation {
let rt = &RUNTIME;
rt.block_on(async move {
// Acquire a connection from the pool
let mut $conn = match pool.get().await {
Ok(conn) => conn,
Err(e) => return format!("Error: {}", e),
let mut $conn = match timeout(pool_get_timeout, pool.get()).await {
Ok(Ok(conn)) => conn,
Ok(Err(e)) => return format!("Error: {}", e),
Err(_) => return "Error: Redis connection checkout timed out".to_string(),
};
// Execute the user-provided Redis operation
$operation
match timeout(command_timeout, async move { $operation }).await {
Ok(result) => result,
Err(_) => "Error: Redis operation timed out".to_string(),
}
})
}};
}

View File

@ -428,9 +428,12 @@ pub fn vgarage_exists(call_context: CallContext, key: String) -> String {
uid
}
None => {
let error_msg = format!("Error: Failed to resolve UID for key: {}", key);
log("v_garage", "ERROR", &error_msg);
return error_msg;
log(
"v_garage",
"WARN",
&format!("Failed to resolve UID for key: {}", key),
);
return "false".to_string();
}
};
@ -438,22 +441,21 @@ pub fn vgarage_exists(call_context: CallContext, key: String) -> String {
Ok(exists) => {
log(
"v_garage",
"INFO",
&format!("Virtual garage exists for: {}", resolved_uid),
"DEBUG",
&format!("Virtual garage '{}' exists: {}", resolved_uid, exists),
);
exists.to_string()
}
Err(e) => {
let error_msg = format!("Error: {}", e);
log(
"v_garage",
"ERROR",
&format!(
"Failed to check if virtual garage exists for '{}': {}",
"Failed to check if virtual garage '{}' exists: {}",
resolved_uid, e
),
);
error_msg
"false".to_string()
}
}
}

View File

@ -424,9 +424,12 @@ pub fn vlocker_exists(call_context: CallContext, key: String) -> String {
uid
}
None => {
let error_msg = format!("Error: Failed to resolve UID for key: {}", key);
log("v_locker", "ERROR", &error_msg);
return error_msg;
log(
"v_locker",
"WARN",
&format!("Failed to resolve UID for key: {}", key),
);
return "false".to_string();
}
};
@ -434,22 +437,21 @@ pub fn vlocker_exists(call_context: CallContext, key: String) -> String {
Ok(exists) => {
log(
"v_locker",
"INFO",
&format!("Virtual locker exists for: {}", resolved_uid),
"DEBUG",
&format!("Virtual locker '{}' exists: {}", resolved_uid, exists),
);
exists.to_string()
}
Err(e) => {
let error_msg = format!("Error: {}", e);
log(
"v_locker",
"ERROR",
&format!(
"Failed to check if virtual locker exists for '{}': {}",
"Failed to check if virtual locker '{}' exists: {}",
resolved_uid, e
),
);
error_msg
"false".to_string()
}
}
}