#How to make functions accessing a table thread safe?

1 messages · Page 1 of 1 (latest)

jaunty umbra
#

I have the following code, and I want to make sure that the actions on the table are thread-safe (and make the accessor functions isolated). How should I go about it?

final table<Patient> key(id) patientTable = table [
    {id: "a0384c7b-d22b-4627-9a2d-dfce02987c96", firstName: "John", lastName: "Doe", dob: {year: 1954, month: 10, day: 23}, gender: "male"},
    {id: "722d6531-a9f3-4eb7-8735-aa7151f9bb39", firstName: "Jane", lastName: "Doe", dob: {year: 1972, month: 9, day: 17}, gender: "female"}
];

service / on EP {
    isolated resource function post patients(@http:Payload PatientEntry patientEntry) returns Patient {
        Patient patient = {id: uuid:createRandomUuid(), ...patientEntry};
        patientTable.put(patient);
        return patient;
    }

    isolated resource function get patients/[string patientId]() returns Patient|http:NotFound {
        if !patientTable.hasKey(patientId) {
            return {body: "Patient not found"};
        }
        return patientTable.get(patientId);
    }
}

I can't make the patientTable readonly, since I need to edit the table from the resource functions. Also tried having locks in all places where I access the patientTable, I still get invalid access of mutable storage in an 'isolated' function(BCE3943)

open wolf
#

If you are fine with storing readonly Patients you can use an isolated class that acts as an wrapper for your table like this,

isolated class Container {
    private final table<Patient & readonly> key(id) patientTable = table [];

    isolated function addPatient(Patient&readonly patient) {
        lock {
            self.patientTable.put(patient);
        }
    }

    isolated function hasPatient(string patientId) returns boolean {
        lock {
            return self.patientTable.hasKey(patientId);
        }
    }

    isolated function getPatient(string patientId) returns Patient {
        lock {
            return self.patientTable.get(patientId);
        }
    }
}

final Container container = new;

service / on new http:Listener(9090) {
    isolated resource function post patients(@http:Payload PatientEntry patientEntry) returns Patient {
        Patient patient = {id: "1", ...patientEntry};
        container.addPatient(patient.cloneReadOnly());
        return patient;
    }

    isolated resource function get patients/[string patientId]() returns Patient|http:NotFound {
        if !container.hasPatient(patientId) {
            return {body: "Patient not found"};
        }
        return container.getPatient(patientId);
    }
}

Not sure if there is a cleaner way (lock work here since it locks the container)

#

Also if you are fine with moving the table in to the service (ie. no need to access it outside of the service) then you can use locks directly as well (here it locks on the service)

service / on new http:Listener(9090) {
    final table<Patient> key(id) patientTable = table [
        {id: "a0384c7b-d22b-4627-9a2d-dfce02987c96", firstName: "John", lastName: "Doe", dob: {year: 1954, month: 10, day: 23}, gender: "male"},
        {id: "722d6531-a9f3-4eb7-8735-aa7151f9bb39", firstName: "Jane", lastName: "Doe", dob: {year: 1972, month: 9, day: 17}, gender: "female"}
    ];
    isolated resource function post patients(@http:Payload PatientEntry patientEntry) returns Patient {
        Patient patient = {id: "1", ...patientEntry};
        lock {
            self.patientTable.put(patient);
        }
        return patient;
    }

    isolated resource function get patients/[string patientId]() returns Patient|http:NotFound {
        lock {
            if !self.patientTable.hasKey(patientId) {
                return {body: "Patient not found"};
            }
            return self.patientTable.get(patientId);
        }
    }
}

vapid fjord
#

Here is another option.
You can make the table isolated.
Use clone function when it necessary

import ballerina/uuid;
import ballerina/http;

type Patient record {readonly string id;};
type PatientEntry record {||};

final isolated table<Patient> key(id) patientTable = table [
    {id: "a0384c7b-d22b-4627-9a2d-dfce02987c96", firstName: "John", lastName: "Doe", dob: {year: 1954, month: 10, day: 23}, gender: "male"},
    {id: "722d6531-a9f3-4eb7-8735-aa7151f9bb39", firstName: "Jane", lastName: "Doe", dob: {year: 1972, month: 9, day: 17}, gender: "female"}
];

isolated service / on new http:Listener(8080) {
    isolated resource function post patients(@http:Payload PatientEntry patientEntry) returns Patient {
        Patient patient = {id: uuid:createRandomUuid(), ...patientEntry};
        lock {
            patientTable.put(patient.clone());
        }
        return patient;
    }

    isolated resource function get patients/[string patientId]() returns Patient|http:NotFound {
        lock {
            if !patientTable.hasKey(patientId) {
                return {body: "Patient not found"};
            }
            return patientTable.get(patientId).clone();
        }
    }
}