odavid / typeorm-transactional-cls-hooked

A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation and Sequelize CLS
MIT License
524 stars 86 forks source link

The @Transactional() decorator doesn't want to open a new transaction #54

Closed herenickname closed 3 years ago

herenickname commented 4 years ago

index.ts

export class Main {
     constructor(
        private readonly service: Service // Are injected from DI system
    ) {} 

     public load() {
          setTimeout(() => this.doJob(), 5000)
     }

     private async doJob() {
          await this.service.makeMeACake()
     }
}

service.ts

export class Service {
    constructor(readonly cakeRepository: CakeRepository) {}

    @Transactional()
    public async makeMeACake() {
        let cake = await this.cakeRepository.findOneOrFail(50, {
            lock: { mode: 'pessimistic_write' }
        })

        cake.status = ECakeStatus.CREATING
        await this.cakeRepository.update(cake.id, { status: cake.status })
    }
}

And here's what I caught when launching the application: image

I don't use http server in this application. I have several functions, timer and service. Unfortunately for some reason the @Transactional() decorator doesn't want to open a new transaction in my case. In the original, the code has more methods and is more complicated but the basic principle of calling functions is exactly the same as I had described. Where can I find the error? How to debug?

odavid commented 4 years ago

Hi @ekifox - do u initialize the cls-hooked before u start the application?

Like this? https://github.com/odavid/typeorm-transactional-cls-hooked#initialization

I have not tried that with "DI", but I believe the above is the main issue (Although, Not sure)

herenickname commented 4 years ago

image

I tried to start transaction by: await this.crashRepository.manager.connection.createQueryRunner().startTransaction() and it does not help.

Yes, I'm do the initialization before start the application: image

Very strange, but other methods from this service are working successfully. The queries are ran in one transaction. But not with finishRound.

herenickname commented 4 years ago

I've found a strange solution for the problem. If setTimeout is set with minus value then the finishRound is ran without transaction. If i will wait for 5 sec. or more the transaction is propagated to finishRound and all the works nice. The only one what I think now is the other function in this context is caught the connection and ran a transaction.

herenickname commented 4 years ago

After a little bit of digging, I've found that I have a separate function that runs in same context at the same time. And that causes the transaction and entity manager capturing from one function to another one.

herenickname commented 4 years ago

So, how to create a new context for other function that runs in the background?

odavid commented 4 years ago
public load() {
   setTimeout(() => this.doJob(), 5000)
}

Seems to me the problem is related to the load() function that is not async and it calls to setTimeout that does not await and does not return a promise. I believe this creates a mess in how cls-hooked is expecting things to work. I am really not an expert here ): - I think you should try to see how to implement the background process in a different manner. Maybe you need to return a promise and make the load() transactional and async...

I am really sorry I cannot help more than that, and also if I am saying "stupid" things 😉

herenickname commented 4 years ago

I believe this creates a mess in how cls-hooked is expecting things to work.

I make some tests with await and without. Yes, cls-hooked really lost context when child called in parent without await :( Things like setImmediate(async () => await this.test()) causes to context losing too.