# Milestone 5: Timer & Callback System **Status**: Complete **Goal**: Safe timer APIs managed by kernel. --- ## Overview This milestone implements JavaScript-style timer APIs (`setTimeout`, `setInterval`) that are: - Managed by the kernel (not Lua coroutines) - Subject to per-app limits - Properly cleaned up when apps stop - Integrated with sandbox instruction counting ### Key Deliverables 1. **TimerManager class** - Central timer management 2. **Lua timer APIs** - setTimeout, setInterval, clearTimeout, clearInterval 3. **Kernel integration** - Fire timers from main loop --- ## File Structure ``` src/main/cpp/sandbox/ ├── lua_sandbox.h # (existing) ├── lua_sandbox.cpp # (existing) ├── timer_manager.h # NEW - Timer management └── timer_manager.cpp # NEW - Implementation ``` --- ## Implementation Details ### 1. TimerManager Class ```cpp // timer_manager.h #pragma once #include #include #include #include #include #include struct lua_State; namespace mosis { using TimerId = uint64_t; using TimePoint = std::chrono::steady_clock::time_point; using Duration = std::chrono::milliseconds; struct Timer { TimerId id; std::string app_id; TimePoint fire_time; Duration interval; // 0 for setTimeout, >0 for setInterval int callback_ref; // Lua registry reference lua_State* L; // Lua state that owns the callback bool cancelled = false; }; class TimerManager { public: TimerManager(); ~TimerManager(); // Create timers (returns timer ID) TimerId SetTimeout(lua_State* L, const std::string& app_id, int callback_ref, int delay_ms); TimerId SetInterval(lua_State* L, const std::string& app_id, int callback_ref, int interval_ms); // Cancel timers bool ClearTimer(const std::string& app_id, TimerId id); // Cancel all timers for an app void ClearAppTimers(const std::string& app_id); // Process timers (call from main loop) // Returns number of timers fired int ProcessTimers(); // Get timer count for an app size_t GetTimerCount(const std::string& app_id) const; // Configuration static constexpr size_t MAX_TIMERS_PER_APP = 100; static constexpr int MIN_INTERVAL_MS = 10; static constexpr int MIN_TIMEOUT_MS = 0; private: struct TimerCompare { bool operator()(const Timer& a, const Timer& b) const { return a.fire_time > b.fire_time; // Min-heap } }; TimerId m_next_id = 1; std::priority_queue, TimerCompare> m_timers; std::unordered_map m_app_timer_counts; mutable std::mutex m_mutex; void FireTimer(Timer& timer); }; // Lua API registration void RegisterTimerAPI(lua_State* L, TimerManager* manager, const std::string& app_id); } // namespace mosis ``` ### 2. Lua Timer APIs ```lua -- setTimeout: fire callback once after delay local id = setTimeout(function() print("Fired after 1000ms") end, 1000) -- clearTimeout: cancel a pending timeout clearTimeout(id) -- setInterval: fire callback repeatedly local id = setInterval(function() print("Fires every 500ms") end, 500) -- clearInterval: stop an interval clearInterval(id) ``` ### 3. Timer Limits | Limit | Value | Reason | |-------|-------|--------| | Max timers per app | 100 | Prevent resource exhaustion | | Min interval | 10ms | Prevent CPU spinning | | Min timeout | 0ms | Allow immediate callbacks | --- ## Test Cases ### Test 1: SetTimeout Fires ```cpp bool Test_SetTimeoutFires(std::string& error_msg) { mosis::TimerManager manager; SandboxContext ctx = TestContext(); LuaSandbox sandbox(ctx); // Register timer API mosis::RegisterTimerAPI(sandbox.GetState(), &manager, ctx.app_id); // Set a timeout sandbox.LoadString(R"( fired = false setTimeout(function() fired = true end, 50) )", "timeout_test"); // Process timers after delay std::this_thread::sleep_for(std::chrono::milliseconds(100)); manager.ProcessTimers(); // Check if callback fired sandbox.LoadString("assert(fired == true)", "check"); return true; } ``` ### Test 2: SetInterval Fires Multiple Times ```cpp bool Test_SetIntervalFires(std::string& error_msg) { mosis::TimerManager manager; SandboxContext ctx = TestContext(); LuaSandbox sandbox(ctx); mosis::RegisterTimerAPI(sandbox.GetState(), &manager, ctx.app_id); sandbox.LoadString(R"( count = 0 setInterval(function() count = count + 1 end, 30) )", "interval_test"); // Process multiple times for (int i = 0; i < 5; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(40)); manager.ProcessTimers(); } // Should have fired multiple times sandbox.LoadString("assert(count >= 3)", "check"); return true; } ``` ### Test 3: ClearTimeout Cancels ```cpp bool Test_ClearTimeoutCancels(std::string& error_msg) { mosis::TimerManager manager; SandboxContext ctx = TestContext(); LuaSandbox sandbox(ctx); mosis::RegisterTimerAPI(sandbox.GetState(), &manager, ctx.app_id); sandbox.LoadString(R"( fired = false local id = setTimeout(function() fired = true end, 100) clearTimeout(id) )", "clear_test"); std::this_thread::sleep_for(std::chrono::milliseconds(150)); manager.ProcessTimers(); // Should NOT have fired sandbox.LoadString("assert(fired == false)", "check"); return true; } ``` ### Test 4: Timer Limit Enforced ```cpp bool Test_TimerLimitEnforced(std::string& error_msg) { mosis::TimerManager manager; SandboxContext ctx = TestContext(); LuaSandbox sandbox(ctx); mosis::RegisterTimerAPI(sandbox.GetState(), &manager, ctx.app_id); // Try to create too many timers sandbox.LoadString(R"( for i = 1, 150 do setTimeout(function() end, 1000000) end )", "limit_test"); // Should be capped at MAX_TIMERS_PER_APP EXPECT_TRUE(manager.GetTimerCount(ctx.app_id) <= 100); return true; } ``` ### Test 5: ClearAppTimers Cleanup ```cpp bool Test_ClearAppTimersCleanup(std::string& error_msg) { mosis::TimerManager manager; SandboxContext ctx = TestContext(); LuaSandbox sandbox(ctx); mosis::RegisterTimerAPI(sandbox.GetState(), &manager, ctx.app_id); sandbox.LoadString(R"( for i = 1, 10 do setTimeout(function() end, 1000000) end )", "cleanup_test"); EXPECT_TRUE(manager.GetTimerCount(ctx.app_id) == 10); // Clear all timers for app manager.ClearAppTimers(ctx.app_id); EXPECT_TRUE(manager.GetTimerCount(ctx.app_id) == 0); return true; } ``` ### Test 6: Minimum Interval Enforced ```cpp bool Test_MinIntervalEnforced(std::string& error_msg) { mosis::TimerManager manager; SandboxContext ctx = TestContext(); LuaSandbox sandbox(ctx); mosis::RegisterTimerAPI(sandbox.GetState(), &manager, ctx.app_id); // Try to set interval less than minimum sandbox.LoadString(R"( count = 0 setInterval(function() count = count + 1 end, 1) -- 1ms, should be clamped to 10ms )", "min_interval_test"); // With 1ms interval, in 50ms we'd get 50 callbacks // With 10ms minimum, we should get ~5 std::this_thread::sleep_for(std::chrono::milliseconds(55)); for (int i = 0; i < 10; i++) { manager.ProcessTimers(); } sandbox.LoadString("assert(count <= 10)", "check"); return true; } ``` --- ## Acceptance Criteria All tests must pass: - [x] `Test_SetTimeoutFires` - Timeout fires after delay - [x] `Test_SetIntervalFires` - Interval fires repeatedly - [x] `Test_ClearTimeoutCancels` - Cancelled timeout doesn't fire - [x] `Test_ClearIntervalCancels` - Cancelled interval stops - [x] `Test_TimerLimitEnforced` - Max 100 timers per app - [x] `Test_ClearAppTimersCleanup` - All app timers cleared on stop - [x] `Test_MinIntervalEnforced` - Interval clamped to 10ms minimum --- ## Next Steps After Milestone 5 passes: 1. Milestone 6: JSON & Crypto APIs 2. Milestone 7: Virtual Filesystem