RWMutex
This commit is contained in:
parent
7d693247c0
commit
22a1c10b4e
4 changed files with 114 additions and 36 deletions
|
|
@ -268,19 +268,25 @@ class StorageProcessor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getManager(txId?: string): DataSource | EntityManager {
|
private getTx(txId: string) {
|
||||||
if (txId) {
|
|
||||||
if (!this.activeTransaction || this.activeTransaction.txId !== txId) {
|
if (!this.activeTransaction || this.activeTransaction.txId !== txId) {
|
||||||
throw new Error('Transaction not found');
|
throw new Error('Transaction not found');
|
||||||
}
|
}
|
||||||
return this.activeTransaction.manager
|
return this.activeTransaction.manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getManager(txId?: string): DataSource | EntityManager {
|
||||||
|
if (txId) {
|
||||||
|
return this.getTx(txId)
|
||||||
|
}
|
||||||
return this.DB
|
return this.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleDelete(operation: DeleteOperation<any>) {
|
private async handleDelete(operation: DeleteOperation<any>) {
|
||||||
const manager = this.getManager(operation.txId);
|
|
||||||
const res = await manager.getRepository(MainDbEntities[operation.entity]).delete(operation.q)
|
const res = await this.handleWrite(operation.txId, eM => {
|
||||||
|
return eM.getRepository(MainDbEntities[operation.entity]).delete(operation.q)
|
||||||
|
})
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
type: 'delete',
|
type: 'delete',
|
||||||
|
|
@ -290,8 +296,9 @@ class StorageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleRemove(operation: RemoveOperation<any>) {
|
private async handleRemove(operation: RemoveOperation<any>) {
|
||||||
const manager = this.getManager(operation.txId);
|
const res = await this.handleWrite(operation.txId, eM => {
|
||||||
const res = await manager.getRepository(MainDbEntities[operation.entity]).remove(operation.q)
|
return eM.getRepository(MainDbEntities[operation.entity]).remove(operation.q)
|
||||||
|
})
|
||||||
|
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -302,8 +309,9 @@ class StorageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleUpdate(operation: UpdateOperation<any>) {
|
private async handleUpdate(operation: UpdateOperation<any>) {
|
||||||
const manager = this.getManager(operation.txId);
|
const res = await this.handleWrite(operation.txId, eM => {
|
||||||
const res = await manager.getRepository(MainDbEntities[operation.entity]).update(operation.q, operation.toUpdate)
|
return eM.getRepository(MainDbEntities[operation.entity]).update(operation.q, operation.toUpdate)
|
||||||
|
})
|
||||||
|
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -314,8 +322,10 @@ class StorageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleIncrement(operation: IncrementOperation<any>) {
|
private async handleIncrement(operation: IncrementOperation<any>) {
|
||||||
const manager = this.getManager(operation.txId);
|
const res = await this.handleWrite(operation.txId, eM => {
|
||||||
const res = await manager.getRepository(MainDbEntities[operation.entity]).increment(operation.q, operation.propertyPath, operation.value)
|
return eM.getRepository(MainDbEntities[operation.entity]).increment(operation.q, operation.propertyPath, operation.value)
|
||||||
|
})
|
||||||
|
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
type: 'increment',
|
type: 'increment',
|
||||||
|
|
@ -325,8 +335,10 @@ class StorageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleDecrement(operation: DecrementOperation<any>) {
|
private async handleDecrement(operation: DecrementOperation<any>) {
|
||||||
const manager = this.getManager(operation.txId);
|
const res = await this.handleWrite(operation.txId, eM => {
|
||||||
const res = await manager.getRepository(MainDbEntities[operation.entity]).decrement(operation.q, operation.propertyPath, operation.value)
|
return eM.getRepository(MainDbEntities[operation.entity]).decrement(operation.q, operation.propertyPath, operation.value)
|
||||||
|
})
|
||||||
|
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
type: 'decrement',
|
type: 'decrement',
|
||||||
|
|
@ -336,8 +348,9 @@ class StorageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleFindOne(operation: FindOneOperation<any>) {
|
private async handleFindOne(operation: FindOneOperation<any>) {
|
||||||
const manager = this.getManager(operation.txId);
|
const res = await this.handleRead(operation.txId, eM => {
|
||||||
const res = await manager.getRepository(MainDbEntities[operation.entity]).findOne(operation.q)
|
return eM.getRepository(MainDbEntities[operation.entity]).findOne(operation.q)
|
||||||
|
})
|
||||||
|
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -348,8 +361,9 @@ class StorageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleFind(operation: FindOperation<any>) {
|
private async handleFind(operation: FindOperation<any>) {
|
||||||
const manager = this.getManager(operation.txId);
|
const res = await this.handleRead(operation.txId, eM => {
|
||||||
const res = await manager.getRepository(MainDbEntities[operation.entity]).find(operation.q)
|
return eM.getRepository(MainDbEntities[operation.entity]).find(operation.q)
|
||||||
|
})
|
||||||
|
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -360,8 +374,9 @@ class StorageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleSum(operation: SumOperation<object>) {
|
private async handleSum(operation: SumOperation<object>) {
|
||||||
const manager = this.getManager(operation.txId);
|
const res = await this.handleRead(operation.txId, eM => {
|
||||||
const res = await manager.getRepository(MainDbEntities[operation.entity]).sum(operation.columnName, operation.q)
|
return eM.getRepository(MainDbEntities[operation.entity]).sum(operation.columnName, operation.q)
|
||||||
|
})
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
type: 'sum',
|
type: 'sum',
|
||||||
|
|
@ -371,7 +386,10 @@ class StorageProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleCreateAndSave(operation: CreateAndSaveOperation<any>) {
|
private async handleCreateAndSave(operation: CreateAndSaveOperation<any>) {
|
||||||
const saved = await this.createAndSave(operation)
|
const saved = await this.handleWrite(operation.txId, async eM => {
|
||||||
|
const res = eM.getRepository(MainDbEntities[operation.entity]).create(operation.toSave)
|
||||||
|
return eM.getRepository(MainDbEntities[operation.entity]).save(res)
|
||||||
|
})
|
||||||
|
|
||||||
this.sendResponse({
|
this.sendResponse({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -381,19 +399,23 @@ class StorageProcessor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createAndSave(operation: CreateAndSaveOperation<any>) {
|
private async handleRead(txId: string | undefined, read: (tx: DataSource | EntityManager) => Promise<any>) {
|
||||||
if (operation.txId) {
|
if (txId) {
|
||||||
const manager = this.getManager(operation.txId);
|
const tx = this.getTx(txId)
|
||||||
const res = manager.getRepository(MainDbEntities[operation.entity]).create(operation.toSave)
|
return read(tx)
|
||||||
return manager.getRepository(MainDbEntities[operation.entity]).save(res)
|
}
|
||||||
|
return this.txQueue.Read(read)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleWrite(txId: string | undefined, write: (tx: DataSource | EntityManager) => Promise<any>) {
|
||||||
|
if (txId) {
|
||||||
|
const tx = this.getTx(txId)
|
||||||
|
return write(tx)
|
||||||
}
|
}
|
||||||
return this.txQueue.PushToQueue({
|
return this.txQueue.PushToQueue({
|
||||||
dbTx: false,
|
dbTx: false,
|
||||||
description: operation.description || "createAndSave",
|
description: "write",
|
||||||
exec: async tx => {
|
exec: write
|
||||||
const res = tx.getRepository(MainDbEntities[operation.entity]).create(operation.toSave)
|
|
||||||
return tx.getRepository(MainDbEntities[operation.entity]).save(res)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,73 @@ type TxOperation<T> = {
|
||||||
dbTx: boolean
|
dbTx: boolean
|
||||||
description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
|
/* type Locks = {
|
||||||
|
beforeQueue: () => Promise<void>
|
||||||
|
afterQueue: () => void
|
||||||
|
} */
|
||||||
export default class {
|
export default class {
|
||||||
DB: DataSource | EntityManager
|
DB: DataSource | EntityManager
|
||||||
pendingTx: boolean
|
pendingTx: boolean
|
||||||
transactionsQueue: { op: TxOperation<any>, res: (v: any) => void, rej: (message: string) => void }[] = []
|
transactionsQueue: { op: TxOperation<any>, res: (v: any) => void, rej: (message: string) => void }[] = []
|
||||||
|
readersQueue: { res: () => void, rej: (message: string) => void }[] = []
|
||||||
|
activeReaders = 0
|
||||||
|
writeRequested = false
|
||||||
log: PubLogger
|
log: PubLogger
|
||||||
|
|
||||||
constructor(name: string, DB: DataSource | EntityManager) {
|
constructor(name: string, DB: DataSource | EntityManager) {
|
||||||
this.DB = DB
|
this.DB = DB
|
||||||
this.log = getLogger({ component: name })
|
this.log = getLogger({ component: name })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeRead(read: (tx: DataSource | EntityManager) => Promise<any>) {
|
||||||
|
try {
|
||||||
|
this.activeReaders++
|
||||||
|
const res = await read(this.DB)
|
||||||
|
this.doneReading()
|
||||||
|
return res
|
||||||
|
} catch (err) {
|
||||||
|
this.doneReading()
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async Read(read: (tx: DataSource | EntityManager) => Promise<any>) {
|
||||||
|
console.log("Read", this.activeReaders, this.pendingTx, this.writeRequested)
|
||||||
|
if (!this.writeRequested) {
|
||||||
|
return this.executeRead(read)
|
||||||
|
}
|
||||||
|
await this.waitWritingDone()
|
||||||
|
return this.executeRead(read)
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitWritingDone() {
|
||||||
|
if (!this.writeRequested) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return new Promise<void>((res, rej) => {
|
||||||
|
this.readersQueue.push({ res, rej })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
doneWriting() {
|
||||||
|
this.writeRequested = false
|
||||||
|
this.readersQueue.forEach(r => {
|
||||||
|
r.res()
|
||||||
|
})
|
||||||
|
this.readersQueue = []
|
||||||
|
}
|
||||||
|
|
||||||
|
doneReading() {
|
||||||
|
this.activeReaders--
|
||||||
|
if (this.activeReaders === 0 && !this.pendingTx) {
|
||||||
|
this.execNextInQueue()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PushToQueue<T>(op: TxOperation<T>) {
|
PushToQueue<T>(op: TxOperation<T>) {
|
||||||
if (!this.pendingTx) {
|
console.log("PushToQueue", this.activeReaders, this.pendingTx, this.writeRequested)
|
||||||
|
this.writeRequested = true
|
||||||
|
if (!this.pendingTx && this.activeReaders === 0) {
|
||||||
return this.execQueueItem(op)
|
return this.execQueueItem(op)
|
||||||
}
|
}
|
||||||
this.log("pushing to queue", this.transactionsQueue.length)
|
this.log("pushing to queue", this.transactionsQueue.length)
|
||||||
|
|
@ -32,6 +86,7 @@ export default class {
|
||||||
this.pendingTx = false
|
this.pendingTx = false
|
||||||
const next = this.transactionsQueue.pop()
|
const next = this.transactionsQueue.pop()
|
||||||
if (!next) {
|
if (!next) {
|
||||||
|
this.doneWriting()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ const start = async () => {
|
||||||
await runTestFile(file, module)
|
await runTestFile(file, module)
|
||||||
} else {
|
} else {
|
||||||
console.log("running all tests")
|
console.log("running all tests")
|
||||||
|
await setupNetwork()
|
||||||
for (const { file, module } of modules) {
|
for (const { file, module } of modules) {
|
||||||
await runTestFile(file, module)
|
await runTestFile(file, module)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,16 @@ import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js'
|
||||||
import { runSanityCheck, safelySetUserBalance, StorageTestBase, TestBase } from './testBase.js'
|
import { runSanityCheck, safelySetUserBalance, StorageTestBase, TestBase } from './testBase.js'
|
||||||
import { FindOptionsWhere } from 'typeorm'
|
import { FindOptionsWhere } from 'typeorm'
|
||||||
export const ignore = false
|
export const ignore = false
|
||||||
export const dev = true
|
export const dev = false
|
||||||
export const requires = 'storage'
|
export const requires = 'storage'
|
||||||
|
|
||||||
export default async (T: StorageTestBase) => {
|
export default async (T: StorageTestBase) => {
|
||||||
const u = await testCanCreateUser(T)
|
const u = await testCanCreateUser(T)
|
||||||
await testCanReadUser(T, u)
|
await testCanReadUser(T, u)
|
||||||
await testConcurrentReads(T, u)
|
await testConcurrentReads(T, u)
|
||||||
T.storage.dbs.setDebug(true)
|
//T.storage.dbs.setDebug(true)
|
||||||
await testTransactionIsolation(T, u)
|
await testTransactionIsolation(T, u)
|
||||||
T.storage.dbs.setDebug(false)
|
//T.storage.dbs.setDebug(false)
|
||||||
await testUserCRUD(T)
|
await testUserCRUD(T)
|
||||||
await testErrorHandling(T, u)
|
await testErrorHandling(T, u)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue