Distributed Smart Parking CECS 327
GitHub

Distributed Smart Parking

Parking sensors, lot servers and a driver app, each a separate process that only communicates by messages. Each lot server owns one lot. There is no central coordinator. Processes can be killed, links cut and replies lost; the rest keeps working.

Team: Degoma, Gonzalez-Morales, Karakra, Lee, Lopez, Nguyen, Ocegueda

Two drivers, one space

alice reserves a space. bob asks for the same one and is denied.

Run scenario 1

Kill a sensor

After 6 s without a report the lot server marks the spaces unknown, not free.

Run scenario 3

Crash a lot server

On restart it replays its log. A reservation made before the crash is still there.

Run scenario 4
Milestone 1

Live demo

Five processes run in this page as Web Workers. The page acts as the network: it adds latency, drops messages and cuts links. Click a process to crash it, a link to cut it, a space to reserve it.

Architecture

0 msgs
Click a process to crash or restart it. Click a link to cut or restore it.
sensor messages client messages peer summaries dropped
250 ms
0%
0%

Scenarios

keys 1 to 5
Scripted runs of the fault cases from the report.
last client reply-
1 to 5 run a scenario, R reset, / search the log, Esc deselect

Message log

0 rows
Every message on the network plus each process's console output. Click a row for the full message.
timefrom → totypedetail
heartbeat 2 s, staleness 6 s, peer summary every 3 s (stale after 9 s), lease 30 s, client timeout 2 s with 3 retries
Milestone 2

Communication

TCP sockets, one JSON envelope for every message (4-byte length header, UTF-8 body), and three patterns: request-reply, publish-subscribe and group communication. Two new processes: a broker and a registry.

Who connects to whom

Each arrow is a TCP connection. Solid ones stay open, dashed ones are opened when needed. A lot server is a service to clients, a subscriber and publisher at the broker, and a member of the lot_servers group.

sensor node broker :5000 lot server registry :5010 client

One exchange per connection

Connect, REQUEST, REPLY, close. The REPLY carries the request's msg_id in reply_to. If nothing comes back in 2 s the client reconnects and sends the same REQUEST again. The service caches replies by msg_id, so the retry gets the cached reply and nothing runs twice.

Unknown op or bad args: status: ERROR. No reply after 3 tries: ServiceUnavailable, written to the log.

Broker in the middle

Subscribers keep one long-lived connection to the broker. A PUBLISH becomes one EVENT per subscriber, sent on that subscriber's connection and logged once per delivery. The publisher gets an ACK with the delivered count. A subscriber that cannot be reached is removed from the topic.

Limits: the broker is a single point of failure, and a slow subscriber delays the others.

Membership at the registry

Members JOIN a group at the registry. To send, a node asks for MEMBERS, then opens one connection per member and sends GROUP_MSG, one log line per target. An unreachable member is logged and skipped. The others still get the message.

No ordering or atomic delivery. Membership can change during a send. Future work: total ordering, acknowledgements, multicast.

Message envelope

Every message is a JSON object with these fields, sent as a 4-byte big-endian length followed by the UTF-8 body.
fieldmeaning
typewhich kind of message; the receiver dispatches on it
msg_idunique id; a REPLY or ACK carries the id it answers in reply_to
senderprocess name, for the logs
timestampISO 8601 with milliseconds, sender's clock, used as ordering evidence
payloadtype-specific data
A bad length header, truncated body or non-JSON bytes raise ProtocolError and the connection is closed. A socket closed mid-read raises ConnectionClosed. Max message size 1 MB.
Eleven message types across the three patterns.
REQUESTrequest-reply

op + args from a client to a service

REPLYrequest-reply

status OK / ERROR, result or error text, reply_to

SUBSCRIBEpub-sub

subscriber asks the broker for a topic

UNSUBSCRIBEpub-sub

subscriber leaves a topic

PUBLISHpub-sub

publisher sends topic + data to the broker

EVENTpub-sub

broker delivers a publish to one subscriber

ACKpub-sub, group

broker / registry acknowledgement, carries delivered count or members

JOINgroup

node joins a group with its address

LEAVEgroup

node leaves a group

MEMBERSgroup

ask the registry who is in a group

GROUP_MSGgroup

sender to each member, one connection each

Browser demo vs Python prototype

The browser demo is a simulation. The Python prototype is the real implementation.
Browser demo (this page)Python prototype (prototype/)
processesWeb Workers, separate memoryOS processes, one per node
transportpostMessage via the page, which acts as the networkTCP sockets, 4-byte length prefix + JSON
patternsrequest-reply, peer summariesrequest-reply, publish-subscribe via broker, group via registry
overlaysimulated in the Milestone 3 section with the same rulesreal mesh over TCP in prototype/p2p, up to five lots
faultskill / restart, cut links, latency, drop, lost replykill any process, lost reply, broker crash, dead group member
logsper-worker in memory, download as .jsonlper-process logs/*.log, JSON lines, replayed on restart
testsfive scripted scenarios10 framing checks, 21 pattern checks, 27 overlay checks, 8 end-to-end scenarios
Milestone 3

Peer-to-peer overlay

The lot servers form an unstructured mesh. Each one keeps a short neighbor list, joins through any running peer, gossips its own summary, keeps replicas of the other lots, and floods a lookup for anything it does not have. There is no coordinator and no rule that says which node owns which key.

Overlay simulator

4 lots, 5 links
Same rules as p2p/overlay.py, starting from the mesh run_demo.sh builds. Click a lot to crash it, click a stopped lot to start it again through a random peer.
join, leave, ping lookup gossip found, reply convergecast duplicate or failed
Membership
Queries
3
0.7
slownormalfast
Live metrics
Event log

Software stack on each lot server

Sensor updates and client requests never deal with routing. The application only calls three functions.
Applicationspace table, find, reserve, release, summary every 3 s
Middlewareput(key, value)   get(key)   lookup(key, ttl)   aggregate()
Overlayneighbor list, join and leave, ping repair, gossip, flood, convergecast
Transportframed JSON over TCP, one port per lot (Milestone 2)
Replicas take load off the owner: a client asking lot A about lot C is answered from A's copy, so only reservations have to reach C. That keeps the arrival rate at each lot below its service rate during a spike.

Measured on the prototype

p2p_metrics.py output
Four lots started by run_demo.sh, numbers rebuilt from each server's neighbor list.
metricvalue
linksA-B, A-C, A-D, B-C, C-D
degreeA 3, B 2, C 3, D 2 (average 2.5)
diameter2 hops (B to D), average path 1.17
lookup cost12 owner-only lookups, all found, 1.17 hops on average, 2 at most
stretch1.00 (the flood always finds the shortest overlay path)
maintenance10 pings every 5 s, 2 messages per second for the whole overlay
gossip miss(1 − 0.7)3 = 0.027 for A and C, (1 − 0.7)2 = 0.09 for B and D
join cost1 round trip to the bootstrap, then 1 per extra neighbor
leave costone message per neighbor, nothing else changes
P2P_JOINmembership

newcomer to one bootstrap peer; ACK carries neighbor pointers and the whole table

P2P_NEIGHBORmembership

ask a peer to link; ACK carries its table

P2P_LEAVEmembership

sent to each neighbor on a graceful exit

P2P_PINGmaintenance

every 5 s; ACK carries a neighbor sample and the table

P2P_GOSSIPpush

key, value, version, origin, path; forwarded with pf

P2P_LOOKUPpull

qid, key, TTL, asker, path; duplicates dropped by qid

P2P_FOUNDpull

sent straight back to the asker with hops and path

P2P_AGGconvergecast

flooded from the root; first sender becomes the parent

P2P_AGG_REPLYconvergecast

partial free and total counts, sent up to the parent

How long is a cached count valid?

Every replica records when it was stored. find reports its age and flags it stale after 12 s, four missed gossip rounds. Clients see an old number labelled as old.

When do updates become visible?

Neighbors see a put within one message. Everyone else in the same round with probability 1 − (1 − pf)n, otherwise within the next round (3 s) or the next ping (5 s), whose answer carries the full table.

How are competing updates handled?

Only the owning lot writes its key, and every write carries a version. A replica keeps a value only if its version is higher. Late or duplicated rumors cannot roll it back. Reservations always go to the owner.

Five lots on the real system

Console lines from the prototype: four lots start, E joins through C and receives four replicas at once, D floods a lookup for B, C leaves, D is killed.

After C leaves, B and E are each down to one link. The random sample in the next ping answer lets them find each other, so the mesh stays in one piece.

Nine new message types

All of them use the Milestone 2 envelope and arrive on the lot server's normal port. JOIN, NEIGHBOR and PING are answered with an ACK on the same connection. The rest are one way.

Every lot is a cache

Each server holds the other lots' summaries, so it answers questions about them without asking. The system is eventually consistent: there is no moment when every lot agrees exactly, but each copy says how old it is.

Why a mesh

degreediameterfor our lots
ring2⌊N/2⌋one break splits it in two lines; updates crawl around
treevariesO(log N)every broadcast passes the root; the root is a bottleneck and a single point of failure
ChordO(log N)O(log N)fast key lookups, but each join moves keys and updates fingers; a broadcast has to be built on top
our mesh2 to 32joins and leaves touch only neighbors; gossip spreads updates with no hot node

Push and pull

push: gossippull: lookup
whenevery 3 s, for each lot's own summarya client asks about a lot that is not cached
spreadsorigin to all neighbors, then each peer forwards with pf = 0.7to all neighbors, TTL drops by one per hop
stopsmessage already seen, or version not newerquery id already seen, or TTL reaches 0
costabout one message per link per roundgrows like d(d − 1)k − 1 at step k
answernone; the value is now in every storeP2P_FOUND straight back to the asker, then cached