move docs to docs/ folder, merge architecture files, update references
This commit is contained in:
333
docs/SANDBOX_MILESTONE_5.md
Normal file
333
docs/SANDBOX_MILESTONE_5.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user