This service implements a VM capacity auction system where:
- Users submit bids (username, volume, price) for VM hours through
/buyendpoints. - Providers add supply through
/sellendpoints. - The system automatically allocates supply to the highest-priced bids (FIFO within price levels).
- Leftover supply persists and auto-matches future bids.
- It is possible to query the allocation for a specific user through the
/allocationendpoint, providing the username as a query parameter.
- Rust 1.78+
# Build the project
cargo build
# Run the server (listens on 0.0.0.0:8080)
cargo run
# Run tests
cargo test
# Run specific test suites
cargo test --lib # Unit tests
cargo test --test property_test # Property tests
cargo test --test concurrency_test # Concurrency testsSubmit a bid for VM capacity.
Request:
{
"username": "user1",
"volume": 100,
"price": 5
}- Response:
200 OK
Behavior:
- If leftover supply exists, immediately allocates what's available.
- Remaining volume is queued as a bid.
- Empty username returns
400 Bad Request. - Zero volume is accepted (no-op).
Add VM capacity supply to the system.
Request:
{
"volume": 100,
}- Response:
200 OK
Behavior:
- Allocates to outstanding bids by price (highest first).
- Within same price level, fills bids in FIFO order.
- Leftover supply is stored for future bids.
Query total allocated VM-hours for a user.
- Response:
200 OK with plain text integer body (e.g., "150")
Error responses:
400 Bad Request- Missing username parameter404 Not Found- Username not found
# Start server
cargo run
# In another terminal:
# User1 bids 100 hours at price 3
curl -s -X POST localhost:8080/buy \
-H 'Content-Type: application/json' \
-d '{"username":"u1","volume":100,"price":3}'
# User2 bids 150 hours at price 2
curl -s -X POST localhost:8080/buy \
-H 'Content-Type: application/json' \
-d '{"username":"u2","volume":150,"price":2}'
# User3 bids 50 hours at price 4 (highest)
curl -s -X POST localhost:8080/buy \
-H 'Content-Type: application/json' \
-d '{"username":"u3","volume":50,"price":4}'
# Provider sells 250 hours
curl -s -X POST localhost:8080/sell \
-H 'Content-Type: application/json' \
-d '{"volume":250}'
# Check allocations
curl -s 'localhost:8080/allocation?username=u1' # Returns: 100
curl -s 'localhost:8080/allocation?username=u2' # Returns: 100 (50 still open)
curl -s 'localhost:8080/allocation?username=u3' # Returns: 50AppState:
bids: Mutex<BTreeMap<u64, BinaryHeap<Bid>>>- Open bids organized by pricesupply: Mutex<u64>- Leftover supply from sellsallocations: Mutex<HashMap<String, u64>>- Total allocated per userseq: AtomicU64- Monotonic sequence for FIFO ordering
Allocation Algorithm:
- Buy: Check leftover supply first, allocate immediately if available, queue remainder as bid
- Sell: Iterate bids from highest to lowest price, fill in FIFO order (via seq), store leftovers in supply
- FIFO guarantee: Atomic sequence counter ensures deterministic ordering under concurrency
I chose BTreeMap<u64, BinaryHeap<Bid>> for bids because:
- BTreeMap is an ordered map, and we need to sort by prices (FIFO inside a price level) so BTreeMap seems like a reasonable DS for this.
- BinaryHeap for the priority queue, we need so sort queues by sequence. I firstly used VecDeque (first thing that came to my mind when I read FIFO), but then while coding I realised that /buy requests can arrive in different order than the order they acquire the lock. Queue is updated after acquiring the lock so it will not necesarily be ordered by a sequence. BinaryHeap will always sort by the sequence.
I chose HashMap<String, u64> for allocations because we want to be able to query users for volume, and the DS does not have to be sorted.
For the sequence I used AtomicU64 counter, we need a lock-free solution for this in order to be able to serve buyers in the order they submit requests.
The system uses coarse-grained locking with three separate mutexes (bids, allocations, supply). I believe this is better than one lock, it creates less contention. There is probably a way to write the project to be lock-free using lock-free data structures and atomic operations but I find it complex for my Rust knowledge level at the moment.