Skip to content

Commit 3217b61

Browse files
committed
Change lock granularity; use locking for all relevant tasks.
1 parent df4d80a commit 3217b61

8 files changed

+48
-65
lines changed

src/actor.ts

+24-43
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,26 @@ export interface Cache<T, G> {
1919
get(...args: any[]): Promise<T>
2020
}
2121

22-
type Task = [string, Date, number]
23-
24-
function timeout(ms: number) {
25-
return new Promise((resolve) => setTimeout(resolve, ms))
22+
interface Task {
23+
name: string
2624
}
2725

2826
export function locked<T, G extends Actor>(
2927
f: (this: G, ...args: any[]) => Promise<T | Error>
3028
): (this: G, ...args: any[]) => Promise<T | Error> {
3129
return async function (this: G, ...args: any[]): Promise<T | Error> {
3230
// we lock the task
33-
if ((await this.lock(f.name)) === false)
34-
return { status: Status.Failed } // we can't obtain a lock
35-
36-
const result = await f.apply(this, args)
37-
38-
// we unlock the task
39-
this.unlock(f.name)
40-
41-
return result
31+
const [task, existing] = this.lock(f.name)
32+
if (existing) return { status: Status.Failed } // we can't obtain a lock
33+
try {
34+
return await f.apply(this, args)
35+
} catch (e) {
36+
console.log(e)
37+
return { status: Status.Failed }
38+
} finally {
39+
// we unlock the task
40+
this.unlock(f.name)
41+
}
4242
}
4343
}
4444

@@ -94,19 +94,15 @@ export class Actor extends Observer {
9494
public actor: string
9595
public id: string
9696

97-
private _taskId: number
98-
private _tasks: Task[]
99-
private _locked: boolean
97+
private _tasks: { [Key: string]: Task }
10098

10199
constructor(actor: string, id: string, backend: Backend) {
102100
// the ID will be used to address local storage so that e.g. we can
103101
// manage multiple providers, users etc. if necessary...
104102

105103
super()
106104

107-
this._taskId = 0
108-
this._tasks = []
109-
this._locked = false
105+
this._tasks = {}
110106

111107
this.actor = actor
112108
this.id = id
@@ -132,36 +128,21 @@ export class Actor extends Observer {
132128
}
133129

134130
unlock(task: string) {
135-
if (this._tasks.length === 0) return false // should never happen
136-
if (this._tasks[0][0] !== task) return false // wrong task order (should not happen)
137-
this._tasks = this._tasks.slice(1)
138-
return true
131+
delete this._tasks[task]
139132
}
140133

141-
async lock(task: string) {
142-
if (this._tasks.find((t: Task) => t[0] === task) !== undefined) {
143-
console.warn(
144-
`Task ${this.actor}::${this.id}::${task} is already in queue, aborting...`
145-
)
146-
return false
134+
lock(task: string): [Task, boolean] {
135+
if (this._tasks[task] === undefined) {
136+
this._tasks[task] = {
137+
name: task,
138+
}
139+
return [this._tasks[task], false]
147140
}
148141

149-
const taskId = this._taskId++
150-
this._tasks.push([task, new Date(), taskId])
151-
152-
while (true) {
153-
if (this._tasks.length === 0) return false // should not happen
154-
const [t, dt, id] = this._tasks[0]
155-
if (id === taskId) break // it's our turn
156-
if (new Date().getTime() - dt.getTime() > 1000 * 10)
157-
// tasks time out after 10 seconds
158-
this._tasks = this._tasks.slice(1)
159-
await timeout(10)
160-
}
161-
return true
142+
return [this._tasks[task], true]
162143
}
163144

164145
clearLocks() {
165-
this._tasks = []
146+
this._tasks = {}
166147
}
167148
}

src/provider/backup-data.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface CloudBackupData extends BackupData {
3030
data: ProviderData | null
3131
}
3232

33-
interface BackupDataResult extends Result {
33+
export interface BackupDataResult extends Result {
3434
data: AESData
3535
}
3636

src/provider/index.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { getAppointments } from "./get-appointments"
1313

1414
import { buf2base32, b642buf } from "../helpers/conversion"
1515
import { randomBytes } from "../crypto"
16-
import { Actor, cached } from "../actor"
16+
import { Actor, cached, locked } from "../actor"
1717
import { Backend } from "../backend"
1818

1919
import {
@@ -28,14 +28,16 @@ import {
2828
export * from "./helpers"
2929

3030
export class Provider extends Actor {
31-
public backupData = backupData
32-
public checkData = checkData
33-
public storeData = storeData
31+
public backupData = locked(backupData)
32+
public checkData = locked(checkData)
33+
public storeData = locked(storeData)
34+
public restoreFromBackup = locked(restoreFromBackup)
35+
public publishAppointments = locked(publishAppointments)
36+
public generateKeyPairs = locked(generateKeyPairs)
37+
public appointments = cached(locked(getAppointments), "appointments")
38+
39+
// helper/convenience functions
3440
public createAppointment = createAppointment
35-
public restoreFromBackup = restoreFromBackup
36-
public publishAppointments = publishAppointments
37-
public generateKeyPairs = generateKeyPairs
38-
public appointments = cached(getAppointments, "appointments")
3941

4042
constructor(id: string, backend: Backend) {
4143
super("provider", id, backend)

src/provider/restore-from-backup.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Provider } from "./"
88
import { LocalBackupData, CloudBackupData } from "./backup-data"
99
import { AESData, Result, Error, Status, ErrorType } from "../interfaces"
1010

11-
interface RestoreFromBackupResult extends Result {
11+
export interface RestoreFromBackupResult extends Result {
1212
data: { [Key: string]: any } | null
1313
}
1414

src/user/backup-data.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface CloudBackupData extends BackupData {
3030
acceptedAppointment: AcceptedAppointment | null
3131
}
3232

33-
interface BackupDataResult extends Result {
33+
export interface BackupDataResult extends Result {
3434
data: AESData
3535
}
3636

src/user/book-appointment.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from "../interfaces"
1616
import { User } from "./"
1717

18-
interface BookAppointmentResult extends Result {
18+
export interface BookAppointmentResult extends Result {
1919
acceptedAppointment: AcceptedAppointment
2020
}
2121

src/user/get-token.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import { User } from "./"
1414
import { Result, Error, ContactData, Status } from "../interfaces"
1515

16-
interface GetTokenResult extends Result {}
16+
export interface GetTokenResult extends Result {}
1717

1818
async function hashContactData(data: ContactData) {
1919
const hashData = {

src/user/index.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ import {
2323
ProviderAppointments,
2424
} from "../interfaces"
2525

26-
import { Actor, cached } from "../actor"
26+
import { Actor, cached, locked } from "../actor"
2727

2828
export class User extends Actor {
29-
public restoreFromBackup = restoreFromBackup
30-
public cancelAppointment = cancelAppointment
31-
public generateKeyPairs = generateKeyPairs
32-
public bookAppointment = bookAppointment
33-
public appointments = cached(getAppointments, "appointments")
34-
public appointment = cached(getAppointment, "appointment")
35-
public backupData = backupData
36-
public getToken = getToken
29+
public restoreFromBackup = locked(restoreFromBackup)
30+
public cancelAppointment = locked(cancelAppointment)
31+
public generateKeyPairs = locked(generateKeyPairs)
32+
public bookAppointment = locked(bookAppointment)
33+
public appointments = cached(locked(getAppointments), "appointments")
34+
public appointment = cached(locked(getAppointment), "appointment")
35+
public backupData = locked(backupData)
36+
public getToken = locked(getToken)
3737

3838
private generateSecret() {
3939
this.secret = buf2base32(b642buf(randomBytes(10)))

0 commit comments

Comments
 (0)