1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-23 18:53:36 +02:00

Server: Fixed transaction locking issue when a sub-transaction fails

This commit is contained in:
Laurent Cozic 2021-06-20 19:30:33 +01:00
parent 5bb68ba65d
commit 12aae48ce6
3 changed files with 19 additions and 56 deletions

View File

@ -227,54 +227,6 @@ export default class ItemModel extends BaseModel<Item> {
return output;
}
// private async folderChildrenItems(userId: Uuid, folderId: string): Promise<Item[]> {
// let output: Item[] = [];
// const rows: Item[] = await this
// .db('user_items')
// .leftJoin('items', 'items.id', 'user_items.item_id')
// .select('items.id', 'items.jop_id', 'items.jop_type')
// .where('items.jop_parent_id', '=', folderId)
// .where('user_items.user_id', '=', userId);
// for (const row of rows) {
// output.push(row);
// if (row.jop_type === ModelType.Folder) {
// const children = await this.folderChildrenItems(userId, row.jop_id);
// output = output.concat(children);
// }
// }
// return output;
// }
// public async shareJoplinFolderAndContent(shareId: Uuid, fromUserId: Uuid, toUserId: Uuid, folderId: string) {
// const folderItem = await this.loadByJopId(fromUserId, folderId, { fields: ['id'] });
// if (!folderItem) throw new ErrorNotFound(`Could not find folder "${folderId}" for share "${shareId}"`);
// const items = [folderItem].concat(await this.folderChildrenItems(fromUserId, folderId));
// const alreadySharedItemIds: string[] = await this
// .db('user_items')
// .pluck('item_id')
// .whereIn('item_id', items.map(i => i.id))
// .where('user_id', '=', toUserId)
// // .where('share_id', '!=', '');
// await this.withTransaction(async () => {
// for (const item of items) {
// if (alreadySharedItemIds.includes(item.id)) continue;
// await this.models().userItem().add(toUserId, item.id, shareId);
// if (item.jop_type === ModelType.Note) {
// const resourceIds = await this.models().itemResource().byItemId(item.id);
// await this.models().share().updateResourceShareStatus(true, shareId, fromUserId, toUserId, resourceIds);
// }
// }
// });
// }
public itemToJoplinItem(itemRow: Item): any {
if (itemRow.jop_type <= 0) throw new Error(`Not a Joplin item: ${itemRow.id}`);
if (!itemRow.content) throw new Error('Item content is missing');
@ -410,7 +362,7 @@ export default class ItemModel extends BaseModel<Item> {
};
}
}
});
}, 'ItemModel::saveFromRawContent');
return output;
}
@ -636,7 +588,7 @@ export default class ItemModel extends BaseModel<Item> {
}
return item;
});
}, 'ItemModel::saveForUser');
}
public async save(_item: Item, _options: SaveOptions = {}): Promise<Item> {

View File

@ -105,14 +105,14 @@ router.get('api/shares/:id', async (path: SubPath, ctx: AppContext) => {
router.get('api/shares', async (_path: SubPath, ctx: AppContext) => {
ownerRequired(ctx);
const items = ctx.models.share().toApiOutput(await ctx.models.share().sharesByUser(ctx.owner.id)) as Share[];
const shares = ctx.models.share().toApiOutput(await ctx.models.share().sharesByUser(ctx.owner.id)) as Share[];
// Fake paginated results so that it can be added later on, if needed.
return {
items: items.map(i => {
items: shares.map(share => {
return {
...i,
...share,
user: {
id: i.owner_id,
id: share.owner_id,
},
};
}),

View File

@ -68,10 +68,21 @@ export default class TransactionHandler {
}
}
// Only the function that started the transaction can rollback it. In
// practice it works as expected even for nested transactions: If any of the
// sub-function throws an error, it will propagate to the parent function,
// which will rollback the connection.
//
// If a sub-function throws an error, but it's catched by the parent, we
// also don't want the transaction to be rollbacked, because the errors are
// essentially managed by the parent function. This is for example how
// ItemModel::saveFromRawContent works because it catches any error and
// saves them to an array, to be returned to the caller. So we don't want
// any error to rollback everything.
public async rollback(txIndex: number): Promise<void> {
this.log(`Rollback transaction: ${txIndex}`);
this.finishTransaction(txIndex);
if (this.activeTransaction_) {
const isLastTransaction = this.finishTransaction(txIndex);
if (isLastTransaction) {
this.log(`Transaction is active - doing rollback: ${txIndex}`);
await this.activeTransaction_.rollback();
this.activeTransaction_ = null;