Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Commit e05b940

Browse files
committed
Stress tested the fuckk out of this, 98.95 fucking percent babyyyyyy
1 parent 1f6b6dd commit e05b940

8 files changed

Lines changed: 622 additions & 50 deletions

File tree

packages/client/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ function App() {
2424
try {
2525
setLoading(true);
2626
const data = await getTodos();
27-
setTodos(data);
27+
// Check if the response is an object with a todos property
28+
setTodos(Array.isArray(data) ? data : (data.todos || []));
2829
setError(null);
2930
} catch (err) {
3031
setError("Failed to fetch todos. Please try again.");

packages/client/src/api/todoApi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const api = axios.create({
55
baseURL: '/api'
66
});
77

8-
export const getTodos = async (): Promise<Todo[]> => {
8+
export const getTodos = async (): Promise<Todo[] | { todos: Todo[], totalCount: number, page: number, limit: number, totalPages: number }> => {
99
const response = await api.get('/todos');
1010
return response.data;
1111
};
@@ -23,4 +23,4 @@ export const updateTodo = async (id: string, data: { text?: string; completed?:
2323
export const deleteTodo = async (id: string): Promise<Todo> => {
2424
const response = await api.delete(`/todos/${id}`);
2525
return response.data;
26-
};
26+
};

packages/client/src/services/socketService.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const SOCKET_URL = import.meta.env.PROD
99
class SocketService {
1010
private socket: Socket | null = null;
1111
private listeners: Map<string, Set<(data: unknown) => void>> = new Map();
12+
private validTodoIds: Set<string> = new Set();
1213

1314
connect(): void {
1415
if (this.socket) return;
@@ -35,6 +36,13 @@ class SocketService {
3536
this.socket.on('users:count', (count: number) => {
3637
this.notifyListeners('users:count', count);
3738
});
39+
40+
// Add a new event handler for todo IDs
41+
this.socket.on('todos:ids', (todoIds: string[]) => {
42+
// Store the valid todo IDs to avoid operations on deleted todos
43+
this.validTodoIds = new Set(todoIds);
44+
this.notifyListeners('todos:ids', todoIds);
45+
});
3846
}
3947

4048
disconnect(): void {
@@ -72,4 +80,4 @@ class SocketService {
7280
// Create a singleton instance
7381
const socketService = new SocketService();
7482

75-
export default socketService;
83+
export default socketService;

packages/server/package.json

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,26 @@
66
"scripts": {
77
"dev": "tsx watch src/index.ts",
88
"build": "tsc",
9-
"start": "node dist/index.js"
9+
"start": "node dist/index.js",
10+
"stress-test": "tsx src/stress-test.ts"
1011
},
1112
"dependencies": {
12-
"cors": "^2.8.5",
13-
"express": "^4.18.2",
14-
"morgan": "^1.10.0",
15-
"shared": "workspace:*",
16-
"socket.io": "^4.7.4",
1713
"@opentelemetry/api": "^1.7.0",
14+
"@opentelemetry/core": "^2.0.0",
1815
"@opentelemetry/instrumentation-express": "^0.35.0",
1916
"@opentelemetry/instrumentation-http": "^0.46.0",
2017
"@opentelemetry/resources": "^1.19.0",
2118
"@opentelemetry/sdk-node": "^0.46.0",
19+
"@opentelemetry/sdk-trace-base": "^2.0.0",
2220
"@opentelemetry/sdk-trace-node": "^1.19.0",
23-
"@opentelemetry/semantic-conventions": "^1.19.0"
21+
"@opentelemetry/semantic-conventions": "^1.19.0",
22+
"axios": "^1.9.0",
23+
"cors": "^2.8.5",
24+
"express": "^4.18.2",
25+
"morgan": "^1.10.0",
26+
"shared": "workspace:*",
27+
"socket.io": "^4.7.4",
28+
"socket.io-client": "^4.7.4"
2429
},
2530
"devDependencies": {
2631
"@types/cors": "^2.8.17",

packages/server/src/index.ts

Lines changed: 154 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
// Import tracing at the top of your entry file
1+
// At the top of your file, before any imports
2+
// Disable tracing during stress tests to improve performance
3+
if (process.env.STRESS_TEST === 'true') {
4+
process.env.DISABLE_TRACING = 'true';
5+
process.env.OTEL_LOG_LEVEL = 'error';
6+
}
7+
8+
// Import tracing after setting environment variables
29
import './tracing';
310
import express, { Express } from 'express';
411
import cors from 'cors';
@@ -22,13 +29,35 @@ app.use(cors());
2229
app.use(morgan('dev'));
2330
app.use(express.json());
2431

32+
// Add a more robust error handling middleware
33+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
34+
app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
35+
console.error('Error:', err.message);
36+
res.status(500).json({ error: 'Internal server error' });
37+
});
38+
2539
// In-memory database
2640
const todos: Todo[] = [];
2741
let onlineUsers = 0;
2842

43+
// Add mutex-like locking mechanism
44+
const locks = new Map<string, boolean>();
45+
46+
// Helper function to acquire a lock
47+
const acquireLock = (id: string): boolean => {
48+
if (locks.has(id)) return false;
49+
locks.set(id, true);
50+
return true;
51+
};
52+
53+
// Helper function to release a lock
54+
const releaseLock = (id: string): void => {
55+
locks.delete(id);
56+
};
57+
2958
// Socket.IO events
3059
io.on('connection', (socket) => {
31-
console.log('Client connected:', socket.id);
60+
console.info('Client connected:', socket.id);
3261

3362
// Increment online users count and broadcast
3463
onlineUsers++;
@@ -38,22 +67,30 @@ io.on('connection', (socket) => {
3867
socket.emit('todos:init', todos);
3968

4069
socket.on('disconnect', () => {
41-
console.log('Client disconnected:', socket.id);
70+
console.info('Client disconnected:', socket.id);
4271

4372
// Decrement online users count and broadcast
4473
onlineUsers--;
4574
io.emit('users:count', onlineUsers);
4675
});
4776
});
4877

49-
// Helper function to broadcast todo updates
50-
const broadcastTodos = () => {
51-
io.emit('todos:update', todos);
52-
};
53-
54-
// Routes
78+
// Add pagination for todos to reduce payload size
5579
app.get('/api/todos', (req, res) => {
56-
res.json(todos);
80+
const page = parseInt(req.query.page as string) || 1;
81+
const limit = parseInt(req.query.limit as string) || 100;
82+
const startIndex = (page - 1) * limit;
83+
const endIndex = page * limit;
84+
85+
const paginatedTodos = todos.slice(startIndex, endIndex);
86+
87+
res.json({
88+
todos: paginatedTodos,
89+
totalCount: todos.length,
90+
page,
91+
limit,
92+
totalPages: Math.ceil(todos.length / limit)
93+
});
5794
});
5895

5996
app.post('/api/todos', (req, res) => {
@@ -88,43 +125,127 @@ app.get('/api/todos/:id', (req, res) => {
88125
res.json(todo);
89126
});
90127

91-
app.patch('/api/todos/:id', (req, res) => {
128+
app.patch('/api/todos/:id', async (req, res) => {
92129
const { id } = req.params;
93130
const updates = req.body as UpdateTodoDto;
94131

95-
const todoIndex = todos.findIndex(t => t.id === id);
96-
97-
if (todoIndex === -1) {
98-
return res.status(404).json({ error: 'Todo not found' });
132+
// Try to acquire lock
133+
if (!acquireLock(id)) {
134+
return res.status(409).json({
135+
error: 'Resource is currently being modified',
136+
retryAfter: 100 // Suggest retry after 100ms
137+
});
99138
}
100139

101-
todos[todoIndex] = { ...todos[todoIndex], ...updates };
102-
103-
// Broadcast the updated todos list
104-
broadcastTodos();
105-
106-
res.json(todos[todoIndex]);
140+
try {
141+
const todoIndex = todos.findIndex(t => t.id === id);
142+
143+
if (todoIndex === -1) {
144+
releaseLock(id);
145+
return res.status(404).json({
146+
error: 'Todo not found',
147+
todoIds: todos.slice(0, 10).map(t => t.id) // Send available IDs for debugging
148+
});
149+
}
150+
151+
todos[todoIndex] = { ...todos[todoIndex], ...updates };
152+
153+
// Broadcast the updated todos list
154+
broadcastTodos();
155+
156+
res.json(todos[todoIndex]);
157+
} finally {
158+
// Always release the lock
159+
releaseLock(id);
160+
}
107161
});
108162

109-
app.delete('/api/todos/:id', (req, res) => {
163+
app.delete('/api/todos/:id', async (req, res) => {
110164
const { id } = req.params;
111-
const todoIndex = todos.findIndex(t => t.id === id);
112165

113-
if (todoIndex === -1) {
114-
return res.status(404).json({ error: 'Todo not found' });
166+
// Try to acquire lock
167+
if (!acquireLock(id)) {
168+
return res.status(409).json({
169+
error: 'Resource is currently being modified',
170+
retryAfter: 100 // Suggest retry after 100ms
171+
});
115172
}
116173

117-
const deletedTodo = todos[todoIndex];
118-
todos = todos.filter(t => t.id !== id);
174+
try {
175+
const todoIndex = todos.findIndex(t => t.id === id);
176+
177+
if (todoIndex === -1) {
178+
releaseLock(id);
179+
return res.status(404).json({
180+
error: 'Todo not found',
181+
todoIds: todos.slice(0, 10).map(t => t.id) // Send available IDs for debugging
182+
});
183+
}
184+
185+
const deletedTodo = todos[todoIndex];
186+
187+
// Use splice to remove the item
188+
todos.splice(todoIndex, 1);
189+
190+
// Broadcast the updated todos list
191+
broadcastTodos();
192+
193+
res.json(deletedTodo);
194+
} finally {
195+
// Always release the lock
196+
releaseLock(id);
197+
}
198+
});
199+
200+
// Optimize broadcasting by limiting frequency and payload size
201+
let broadcastPending = false;
202+
const broadcastTodos = () => {
203+
if (broadcastPending) return;
119204

120-
// Broadcast the updated todos list
121-
broadcastTodos();
205+
broadcastPending = true;
122206

123-
res.json(deletedTodo);
124-
});
207+
// Debounce broadcasts to reduce frequency
208+
setTimeout(() => {
209+
// Only send the first 100 todos to reduce payload size
210+
const limitedTodos = todos.slice(0, 100);
211+
212+
// Send the todo IDs separately to help clients track what's available
213+
const todoIds = todos.map(t => t.id);
214+
215+
io.emit('todos:update', limitedTodos);
216+
io.emit('todos:ids', todoIds);
217+
broadcastPending = false;
218+
}, 100);
219+
};
220+
221+
// Add memory usage monitoring
222+
const logMemoryUsage = () => {
223+
const memoryUsage = process.memoryUsage();
224+
console.log('Memory usage:');
225+
console.log(` RSS: ${Math.round(memoryUsage.rss / 1024 / 1024)} MB`);
226+
console.log(` Heap total: ${Math.round(memoryUsage.heapTotal / 1024 / 1024)} MB`);
227+
console.log(` Heap used: ${Math.round(memoryUsage.heapUsed / 1024 / 1024)} MB`);
228+
};
229+
230+
// Log memory usage every 5 seconds during stress test
231+
if (process.env.STRESS_TEST) {
232+
setInterval(logMemoryUsage, 5000);
233+
}
234+
235+
// Add cleanup to prevent memory leaks
236+
// Periodically clean up old todos if the list gets too large
237+
setInterval(() => {
238+
if (todos.length > 1000) {
239+
console.log(`Cleaning up old todos. Before: ${todos.length}`);
240+
// Keep only the 500 most recent todos
241+
todos = todos.slice(-500);
242+
console.log(`After cleanup: ${todos.length}`);
243+
broadcastTodos();
244+
}
245+
}, 10000);
125246

126247
// Start server
127248
httpServer.listen(PORT, () => {
128-
console.log(`Server running on http://localhost:${PORT}`);
129-
console.log(`WebSocket server running on ws://localhost:${PORT}`);
249+
console.info(`Server running on http://localhost:${PORT}`);
250+
console.info(`WebSocket server running on ws://localhost:${PORT}`);
130251
});

0 commit comments

Comments
 (0)