diff --git a/SOCKETBOX_FIX_SUMMARY.md b/SOCKETBOX_FIX_SUMMARY.md new file mode 100644 index 00000000..bbb5af49 --- /dev/null +++ b/SOCKETBOX_FIX_SUMMARY.md @@ -0,0 +1,99 @@ +# SocketBox State Preservation Fix - Implementation Summary + +## Problem Description +In the "Everything is Box" design, stateful boxes like SocketBox were losing their state across field accesses due to `clone_box()` creating new instances instead of sharing references. + +**Original Issue**: +```nyash +me.server.bind("127.0.0.1", 8080) // ✅ SocketBox ID=10, is_server=true +me.server.isServer() // ❌ SocketBox ID=19, is_server=false (別インスタンス!) +``` + +## Root Cause Analysis +The issue was in three key locations where `clone_box()` was called: +1. `src/interpreter/core.rs:366` - `resolve_variable()` +2. `src/instance.rs:275` - `get_field()` +3. `src/interpreter/expressions.rs:779` - `execute_field_access()` + +Each access to `me.server` was creating a new SocketBox instance via `clone_box()`, so state changes made to one instance weren't visible in subsequent accesses. + +## Solution Implemented + +### Phase 1: Type Infrastructure +- Added `SharedNyashBox = Arc` type alias +- Added `clone_arc()` method to NyashBox trait for Arc conversion + +### Phase 2: Data Structure Updates +- Updated `InstanceBox.fields` from `HashMap>` to `HashMap` +- Updated `NyashInterpreter.local_vars` and `outbox_vars` to use `SharedNyashBox` +- Modified save/restore methods to convert between Arc and Box appropriately + +### Phase 3: Core Reference Sharing +- **`resolve_variable()`**: Now returns `SharedNyashBox` and uses `Arc::clone()` instead of `clone_box()` +- **`get_field()`**: Now returns `SharedNyashBox` and uses `Arc::clone()` instead of `clone_box()` +- **`execute_field_access()`**: Now returns `SharedNyashBox` to preserve sharing + +### Phase 4: Target Fix - SocketBox Clone Implementation +**Key Innovation**: Modified SocketBox Clone to share mutable state: + +```rust +// Before (problematic): +is_server: Arc::new(Mutex::new(current_is_server)), // New state container +is_connected: Arc::new(Mutex::new(current_is_connected)), // New state container + +// After (fixed): +is_server: Arc::clone(&self.is_server), // Share the same state container +is_connected: Arc::clone(&self.is_connected), // Share the same state container +``` + +This ensures that even when SocketBox instances are cloned, they share the same underlying state containers. + +### Phase 5: Interface Compatibility +- Fixed all callers of `resolve_variable()` to handle `SharedNyashBox` return type +- Updated method calls and field access to properly dereference Arc references +- Maintained Box-based external interfaces while using Arc internally + +## How the Fix Works + +1. **Storage**: Variables and fields are stored as `Arc` internally +2. **Access**: Field access returns `Arc::clone()` of the stored reference +3. **Cloning**: When Arc is converted to Box via `clone_box()`, SocketBox creates a new instance BUT shares the same state containers +4. **State Sharing**: All clones of the same original SocketBox share `is_server` and `is_connected` state + +**Result**: +```nyash +me.server.bind("127.0.0.1", 8080) // ✅ SocketBox clone A, is_server=true (shared state) +me.server.isServer() // ✅ SocketBox clone B, is_server=true (same shared state!) +``` + +## Testing + +### Test Files Created: +- `test_final_validation.nyash` - Replicates the exact issue scenario +- `test_complete_socketbox_fix.nyash` - Comprehensive SocketBox testing +- `test_multiple_stateful_boxes.nyash` - Tests multiple stateful box types +- `test_arc_fix.nyash` - Original issue test case + +### Expected Results: +All tests should show that `isServer()` returns `true` after `bind()` is called, confirming that state is preserved across field accesses. + +## Impact on Other Stateful Boxes + +The fix benefits all stateful boxes: +- **SocketBox**: Fixed with custom Clone implementation +- **HTTPServerBox**: Already uses `Arc::clone()` correctly +- **FutureBox**: Already uses `Arc::clone()` correctly +- **DebugBox**: Uses derived Clone with Arc fields (works correctly) +- **FileBox**: Uses derived Clone with Arc fields (works correctly) +- **P2PBox**: Designed as `Arc>` wrapper (already works correctly) + +## Compatibility + +- **External Interfaces**: Preserved Box-based APIs for backward compatibility +- **Internal Storage**: Enhanced with Arc-based sharing for stateful objects +- **Performance**: Minimal overhead from Arc reference counting +- **Memory Safety**: Maintains Rust's ownership guarantees + +## Conclusion + +This fix solves the fundamental issue in the "Everything is Box" design where stateful boxes were losing state due to unnecessary cloning. The hybrid approach maintains interface compatibility while enabling true reference sharing for stateful objects. \ No newline at end of file diff --git a/test_final_validation.nyash b/test_final_validation.nyash new file mode 100644 index 00000000..dba8eec0 --- /dev/null +++ b/test_final_validation.nyash @@ -0,0 +1,51 @@ +// Final validation test for the SocketBox state preservation fix +// This test replicates the exact scenario described in issue #74 + +static box Main { + init { server } + + main() { + print("=== Issue #74 Validation Test ===") + print("Testing SocketBox state preservation across field accesses") + print("") + + // Create SocketBox and store in field + me.server = new SocketBox() + + print("Step 1: Initial state") + print("me.server.isServer(): " + me.server.isServer()) // Should be false + print("") + + print("Step 2: Calling bind() to set server state") + me.server.bind("127.0.0.1", 8080) // This should set is_server=true + print("bind() completed") + print("") + + print("Step 3: Checking state after bind()") + print("me.server.isServer(): " + me.server.isServer()) // Should be true if fix works! + print("") + + // Additional tests: multiple accesses should all return same state + print("Step 4: Multiple field access test") + local server1 + server1 = me.server + print("server1.isServer(): " + server1.isServer()) // Should be true + + local server2 + server2 = me.server + print("server2.isServer(): " + server2.isServer()) // Should be true + + print("") + print("=== Test Summary ===") + if (me.server.isServer()) { + print("✅ SUCCESS: SocketBox state preserved across field accesses!") + print(" The 'Everything is Box' design now correctly shares state") + print(" for stateful boxes like SocketBox, P2PBox, etc.") + } else { + print("❌ FAILURE: SocketBox state was not preserved") + print(" The fix did not work as expected") + } + + return me.server.isServer() + } +} \ No newline at end of file