fabric-gateway-java icon indicating copy to clipboard operation
fabric-gateway-java copied to clipboard

Massive thread creation on listening for events -> unable to create new native thread

Open siglesiasg opened this issue 3 years ago • 3 comments

Hello,

I've found a memory / thread leak when listening to blockchain events, in this case is when trying to listen for block events. Too many threads are created and an error: java.lang.OutOfMemoryError: unable to create new native thread is throwed resulting in a lock when processing events.

How to replicate Case:

I create a new channel and I execute lots of transactions in the channel so a lot of new blocks are generated > 1000. Then I start listening to that channel, using network.addBlockListener(startBlock, blockConsumer); where startBlock is = 0 and blockConsumer is a simple consumer to print the BlockEvent. When I launch the app in debug mode I see that lots of threads are created and waiting for the previous block to be processed.

image

As many threads as available blocks are being created:

image

Ending in a java.lang.OutOfMemoryError: unable to create new native thread

siglesiasg avatar Sep 01 '21 15:09 siglesiasg

Thank you for the issue report and investigation. Is it possible that you are hitting a failure in allocating threads used to deliver events because you have blocked processing of the events with a break-point in your debugger, or is this something you observe in normal (non-debugging) usage? I'm pretty sure I've successfully replayed many tens of thousands of events in the past, but my event listener was very simple and processed events very quickly.

It sounds like there might be scope to improve the way incoming events are handled internally to avoid locking preventing those events from being buffered ready for dispatch to listeners here:

https://github.com/hyperledger/fabric-gateway-java/blob/26c69ebc3ca5f23d0a8ee5efa9ffbd962a78f54b/src/main/java/org/hyperledger/fabric/gateway/impl/event/OrderedBlockEventSource.java#L64-L73

A side-effect to consider is that, depending on the implementation, this may allow that event buffer to grow unbounded and eventually cause out of memory errors.

bestbeforetoday avatar Sep 22 '21 13:09 bestbeforetoday

Hello Mark, thanks for the reply.

We've already seen this issue in production environment and I just had replicated in localhost to check what was going on. At this point we are storing the block events in an SQL so we get the JSON, we parse and we write it on to the database in a transactional way, every block takes about 20/100ms to be executed (depending on tx number). Not really big number.

To solve this issue, we've managed to read ahead the blocks one by one until last one using queryBlockByNumber function and when we finish iterating all available blocks we delegate control to the real event listener. Tricky workaround but is working for now.

I think thread generation is below the method you've posted here but I still don't understand why is necessary to have one thread per block

Thanks

siglesiasg avatar Sep 23 '21 10:09 siglesiasg

I don't think it is strictly necessary to create a new thread to deliver each block, although I guess the intent was to deal with event delivery asynchronously and not hold up any other processing. I'm surprised the threads aren't being allocated from a bounded pool.

You're correct that the thread generation is further down the stack, within the Channel registerBlockListener() method (in fabric-sdk-java) called here:

https://github.com/hyperledger/fabric-gateway-java/blob/26c69ebc3ca5f23d0a8ee5efa9ffbd962a78f54b/src/main/java/org/hyperledger/fabric/gateway/impl/event/ChannelBlockEventSource.java#L40

It might be better if this overload was used instead as a mechanism for delivering events into the fabric-gateway-java buffer before being dispatched to listeners, which I hope would avoid the thread creation:

https://github.com/hyperledger/fabric-sdk-java/blob/87e99f63091db3bc946a2bdfc0c387d985d53e93/src/main/java/org/hyperledger/fabric/sdk/Channel.java#L5806

String registerBlockListener(BlockingQueue<QueuedBlockEvent> blockEventQueue)

bestbeforetoday avatar Sep 23 '21 14:09 bestbeforetoday