334 lines
8.1 KiB
Markdown
334 lines
8.1 KiB
Markdown
# 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 <string>
|
|
#include <functional>
|
|
#include <queue>
|
|
#include <unordered_map>
|
|
#include <chrono>
|
|
#include <mutex>
|
|
|
|
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<Timer, std::vector<Timer>, TimerCompare> m_timers;
|
|
std::unordered_map<std::string, size_t> 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
|