6.102
6.102 — Software Construction
Spring 2025

Code Examples for Reading 18

Download

You can also download a ZIP file containing this code.

After downloading and unpacking the ZIP file:

  • npm install to install package dependencies;
  • npm run to see a list of the ways this code can be run.
    • For example, if npm run shows that there are scripts called foo and bar, you can run the code with either npm run foo or npm run bar.
    • Two conventional script names are test and start. If the code includes one of those script names, then you can omit run and just say npm test or npm start.
Seems to be necessary to have a triple-backtick block at the start of this page,
otherwise all the pre/code tags below aren't syntax highlighted.
So create a hidden one.

drinksfridge.ts

import assert from 'node:assert';
import { MessagePort } from 'node:worker_threads';

/** Record type for DrinksFridge's reply messages. */
export type FridgeResult = {
    drinksTakenOrAdded: number, // number of drinks removed from fridge (if >0) or added to fridge (if <0)
    drinksLeftInFridge: number 
};

/**
 * A mutable type representing a refrigerator containing drinks.
 */
export class DrinksFridge {

    private drinksInFridge: number = 0;

    // Abstraction function:
    //   AF(drinksInFridge, port) = a refrigerator containing `drinksInFridge` drinks
    //                              that takes requests from and sends replies to `port`
    // Rep invariant:
    //   drinksInFridge >= 0

    /**
     * Make a DrinksFridge that will listen for requests and generate replies.
     * 
     * @param port port to receive requests from and send replies to
     */
    public constructor(
        private readonly port: MessagePort
    ) {
        this.checkRep();
    }

    private checkRep(): void {
        assert(this.drinksInFridge >= 0);
    }

    /** Start handling drink requests. */
    public start(): void {
        this.port.on('message', (n: number) => {
            const reply: FridgeResult = this.handleDrinkRequest(n);
            this.port.postMessage(reply);
        });
    }

    /** @param n number of drinks requested from the fridge (if >0), 
                                  or to load into the fridge (if <0) */
    private handleDrinkRequest(n: number): FridgeResult {
        const change = Math.min(n, this.drinksInFridge);
        this.drinksInFridge -= change;
        this.checkRep();
        return { drinksTakenOrAdded: change, drinksLeftInFridge: this.drinksInFridge };
    }
}

loadfridge.ts

import assert from 'node:assert';
import { Worker, isMainThread, parentPort } from 'node:worker_threads';

import { DrinksFridge, FridgeResult } from './drinksfridge.js';

//
// Create and use a drinks fridge.
//

/** Runs on the main thread. */
function main() {
    const worker = new Worker('./dist/loadfridge.js');
    worker.on('message', (result: FridgeResult) => {
      console.log('result from worker', result);
    });
    worker.postMessage(-42);
    worker.postMessage(2);
    
    // abruptly end the program, including the worker, after a short time
    setTimeout(() => process.exit(0), 1000);
}

/** Runs in a worker. */
function worker() {
    assert(parentPort);
    new DrinksFridge(parentPort).start();
}

if (isMainThread) {
    main();
} else {
    worker();
}

server.ts

import express, { Request, Response } from 'express';
import { timeout } from './timeout.js';
import { StatusCodes } from 'http-status-codes';

const app = express();

const PORT = 8000;
app.listen(PORT).on('listening', () => console.log(`now listening at http://localhost:${PORT}`));

// GET /echo?greeting=<string>
//
// response is a greeting including <string>
app.get('/echo', (request: Request, response: Response) => {
    const greeting = request.query['greeting'];
    response
        .status(StatusCodes.OK)
        .type('text')
        .send(greeting + ' to you too!');
});

// GET /wait
//
// waits for 5 seconds before finishing response
app.get('/wait', async (request: Request, response: Response) => {
    await timeout(5000);
    response
        .status(StatusCodes.OK)
        .type('text')
        .send('done');
});

// GET /bad
//
// always produces an error output
app.get('/bad', (request: Request, response: Response) => {
    throw new Error('oof');
});

// GET /wait-bad
//
// waits 1 second, then produces an error output
app.get('/wait-bad', async (request: Request, response: Response) => {
    await timeout(1000);
    throw new Error('oof');
});