6.102
6.102 — Software Construction
Spring 2023

Code Examples for Reading 18

Download

You can also download a ZIP file containing this code.

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 'assert';
import { MessagePort } from 'worker_threads';

/** Record type for DrinksFridge's reply messages. */
export type FridgeResult = {
    drinksTakenOrAdded: number,
    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.addListener('message', (n: number) => {
            const reply: FridgeResult = this.handleDrinkRequest(n);
            this.port.postMessage(reply);
        });
    }

    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 'assert';
import { Worker, isMainThread, parentPort } from 'worker_threads';

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

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

/** Runs on the main thread. */
function main() {
    const worker = new Worker('./dist/loadfridge.js');
    worker.addListener('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';
import HttpStatus from 'http-status-codes';
import asyncHandler from 'express-async-handler';

const app = express();

const PORT = 8000;
app.listen(PORT);
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(HttpStatus.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(HttpStatus.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', asyncHandler(async (request: Request, response: Response) => {
    await timeout(1000);
    throw new Error('oof');
}));