Files
MosisService/SANDBOX_MILESTONE_5.md

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