Race condition
Need
Prevention of race conditions in the system
Context
- Usage of Dart 2.0 for building scalable and performant applications
- Usage of dart:async for asynchronous programming in Dart
- Usage of Shelf for managing data storage and retrieval
- Usage of shelf_io for managing and organizing data in a Node.js application
Description
Non compliant code
import 'dart:async';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
shelf.Response handleRequest(shelf.Request request) {
var path = request.url.path;
if (path == 'start') {
startProcess();
} else if (path == 'end') {
endProcess();
}
return shelf.Response.ok('Request handled');
}
Future<void> startProcess() async {
// some time-consuming process
await Future.delayed(Duration(seconds: 2));
print('Process started');
}
Future<void> endProcess() async {
// some time-consuming process
await Future.delayed(Duration(seconds: 2));
print('Process ended');
}
void main() {
var handler = const shelf.Pipeline().addMiddleware(shelf.logRequests()).addHandler(handleRequest);
io.serve(handler, 'localhost', 8080).then((server) {
print('Serving at http://${server.address.host}:${server.port}');
});
}
In the above code snippet, we have a server application that uses the Shelf framework. This application has two endpoints: /start
and /end
. When a request is received at the /start
endpoint, it initiates a process (simulated by a delay of 2 seconds), and when a request is received at the /end
endpoint, it ends the process (also simulated by a delay of 2 seconds).
The vulnerability lies in the fact that these two processes are independent and don't check the state of each other. This leads to a race condition where if the /end
request arrives before the /start
request has completed, the system will try to end a process that hasn't started yet, leading to unexpected behavior.
For example, if two requests are made almost simultaneously, one to /start
and one to /end
, there's no guarantee that the start process will complete before the end process begins. If the end process tries to end a process that hasn't started, it could lead to errors or unexpected behavior.
This is a classic example of a race condition, where the order and timing of events can affect the system's behavior. In this case, the system assumes that the /start
request will always complete before the /end
request is received, which may not always be the case.
Steps
- Introduce a locking mechanism to ensure that the 'startProcess' and 'endProcess' functions are not executed concurrently.
- Use a boolean flag to track if a process is currently running. If a process is running, the 'startProcess' function should not be able to start another one.
- Ensure that the 'endProcess' function can only be called after the 'startProcess' function has completed.
- Consider using a queue to manage the order of function calls if the order of requests is important.
Compliant code
import 'dart:async';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
bool isProcessRunning = false;
shelf.Response handleRequest(shelf.Request request) {
var path = request.url.path;
if (path == 'start') {
if (!isProcessRunning) {
startProcess();
}
} else if (path == 'end') {
if (isProcessRunning) {
endProcess();
}
}
return shelf.Response.ok('Request handled');
}
Future<void> startProcess() async {
isProcessRunning = true;
// some time-consuming process
await Future.delayed(Duration(seconds: 2));
print('Process started');
}
Future<void> endProcess() async {
// some time-consuming process
await Future.delayed(Duration(seconds: 2));
print('Process ended');
isProcessRunning = false;
}
void main() {
var handler = const shelf.Pipeline().addMiddleware(shelf.logRequests()).addHandler(handleRequest);
io.serve(handler, 'localhost', 8080).then((server) {
print('Serving at http://${server.address.host}:${server.port}');
});
}
The code above introduces a locking mechanism to prevent race conditions. A boolean flag isProcessRunning
is used to track whether a process is currently running.
In the handleRequest
function, before starting a process with startProcess
, it checks if a process is already running by checking the isProcessRunning
flag. If a process is running, it will not start another one, preventing multiple processes from running concurrently.
Similarly, before ending a process with endProcess
, it checks if a process is currently running. If no process is running, it will not attempt to end a process, ensuring that the endProcess
function can only be called after the startProcess
function has completed.
In the startProcess
function, the isProcessRunning
flag is set to true at the beginning, indicating that a process is running. In the endProcess
function, the isProcessRunning
flag is set to false at the end, indicating that no process is running.
This solution ensures that the startProcess
and endProcess
functions are not executed concurrently, preventing race conditions. If the order of requests is important, consider using a queue to manage the order of function calls.