diff --git a/arma/server/addons/actor/XEH_preInit.sqf b/arma/server/addons/actor/XEH_preInit.sqf index 0e1c33d..46cf2f8 100644 --- a/arma/server/addons/actor/XEH_preInit.sqf +++ b/arma/server/addons/actor/XEH_preInit.sqf @@ -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); diff --git a/arma/server/addons/actor/functions/fnc_initActorStore.sqf b/arma/server/addons/actor/functions/fnc_initActorStore.sqf index 5bba888..a82e17d 100644 --- a/arma/server/addons/actor/functions/fnc_initActorStore.sqf +++ b/arma/server/addons/actor/functions/fnc_initActorStore.sqf @@ -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 }] ]; diff --git a/arma/server/addons/bank/functions/fnc_initBankStore.sqf b/arma/server/addons/bank/functions/fnc_initBankStore.sqf index 1e6fe95..ef619e7 100644 --- a/arma/server/addons/bank/functions/fnc_initBankStore.sqf +++ b/arma/server/addons/bank/functions/fnc_initBankStore.sqf @@ -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); diff --git a/arma/server/addons/extension/functions/fnc_extCall.sqf b/arma/server/addons/extension/functions/fnc_extCall.sqf index e56b7e4..b2bab27 100644 --- a/arma/server/addons/extension/functions/fnc_extCall.sqf +++ b/arma/server/addons/extension/functions/fnc_extCall.sqf @@ -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; diff --git a/arma/server/addons/garage/functions/fnc_initGarageStore.sqf b/arma/server/addons/garage/functions/fnc_initGarageStore.sqf index 2cf736d..b714040 100644 --- a/arma/server/addons/garage/functions/fnc_initGarageStore.sqf +++ b/arma/server/addons/garage/functions/fnc_initGarageStore.sqf @@ -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); diff --git a/arma/server/addons/garage/functions/fnc_initVGStore.sqf b/arma/server/addons/garage/functions/fnc_initVGStore.sqf index 36baf05..cb16dbe 100644 --- a/arma/server/addons/garage/functions/fnc_initVGStore.sqf +++ b/arma/server/addons/garage/functions/fnc_initVGStore.sqf @@ -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); diff --git a/arma/server/addons/locker/functions/fnc_initLockerStore.sqf b/arma/server/addons/locker/functions/fnc_initLockerStore.sqf index e24262d..efb085b 100644 --- a/arma/server/addons/locker/functions/fnc_initLockerStore.sqf +++ b/arma/server/addons/locker/functions/fnc_initLockerStore.sqf @@ -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); diff --git a/arma/server/addons/locker/functions/fnc_initVAStore.sqf b/arma/server/addons/locker/functions/fnc_initVAStore.sqf index 013449b..92aecd5 100644 --- a/arma/server/addons/locker/functions/fnc_initVAStore.sqf +++ b/arma/server/addons/locker/functions/fnc_initVAStore.sqf @@ -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); diff --git a/arma/server/addons/org/XEH_preInit.sqf b/arma/server/addons/org/XEH_preInit.sqf index 2b7b560..5be4b92 100644 --- a/arma/server/addons/org/XEH_preInit.sqf +++ b/arma/server/addons/org/XEH_preInit.sqf @@ -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), { diff --git a/arma/server/addons/org/functions/fnc_initOrgStore.sqf b/arma/server/addons/org/functions/fnc_initOrgStore.sqf index 582d3af..540ba7e 100644 --- a/arma/server/addons/org/functions/fnc_initOrgStore.sqf +++ b/arma/server/addons/org/functions/fnc_initOrgStore.sqf @@ -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]]; diff --git a/arma/server/config.example.toml b/arma/server/config.example.toml index eddb170..cce0abb 100644 --- a/arma/server/config.example.toml +++ b/arma/server/config.example.toml @@ -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 diff --git a/arma/server/extension/config.example.toml b/arma/server/extension/config.example.toml index 2f0e061..f556f92 100644 --- a/arma/server/extension/config.example.toml +++ b/arma/server/extension/config.example.toml @@ -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 diff --git a/arma/server/extension/src/actor.rs b/arma/server/extension/src/actor.rs index 6bbcd5f..f11f103 100644 --- a/arma/server/extension/src/actor.rs +++ b/arma/server/extension/src/actor.rs @@ -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( diff --git a/arma/server/extension/src/bank.rs b/arma/server/extension/src/bank.rs index bac62a8..5e3d85e 100644 --- a/arma/server/extension/src/bank.rs +++ b/arma/server/extension/src/bank.rs @@ -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( diff --git a/arma/server/extension/src/garage.rs b/arma/server/extension/src/garage.rs index 38690d0..c1488a2 100644 --- a/arma/server/extension/src/garage.rs +++ b/arma/server/extension/src/garage.rs @@ -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() } } } diff --git a/arma/server/extension/src/locker.rs b/arma/server/extension/src/locker.rs index 0f42ac5..c20b3c4 100644 --- a/arma/server/extension/src/locker.rs +++ b/arma/server/extension/src/locker.rs @@ -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() } } } diff --git a/arma/server/extension/src/redis/client.rs b/arma/server/extension/src/redis/client.rs index 3067ac9..0ebc910 100644 --- a/arma/server/extension/src/redis/client.rs +++ b/arma/server/extension/src/redis/client.rs @@ -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; @@ -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) diff --git a/arma/server/extension/src/redis/config.rs b/arma/server/extension/src/redis/config.rs index eaadfa5..0b3c9df 100644 --- a/arma/server/extension/src/redis/config.rs +++ b/arma/server/extension/src/redis/config.rs @@ -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 = 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, /// Idle connection timeout in seconds pub idle_timeout: Option, + /// Maximum time to wait for pool connection checkout in milliseconds + pub pool_get_timeout_ms: Option, + /// Maximum time to wait for individual Redis command execution in milliseconds + pub command_timeout_ms: Option, + /// Maximum time to wait for pool connection establishment in milliseconds + pub connect_timeout_ms: Option, } 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::(&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::(&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() } diff --git a/arma/server/extension/src/redis/macros.rs b/arma/server/extension/src/redis/macros.rs index 6115b6e..80e40ff 100644 --- a/arma/server/extension/src/redis/macros.rs +++ b/arma/server/extension/src/redis/macros.rs @@ -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(), + } }) }}; } diff --git a/arma/server/extension/src/v_garage.rs b/arma/server/extension/src/v_garage.rs index 0b015df..17506e9 100644 --- a/arma/server/extension/src/v_garage.rs +++ b/arma/server/extension/src/v_garage.rs @@ -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() } } } diff --git a/arma/server/extension/src/v_locker.rs b/arma/server/extension/src/v_locker.rs index 8b4d98a..11be05e 100644 --- a/arma/server/extension/src/v_locker.rs +++ b/arma/server/extension/src/v_locker.rs @@ -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() } } }