Unable to catch exception in java.lang.Thread

I have been messing with this for a while, and can't seem to figure out what I am missing.

I am trying to catch exceptions in a pool of async threads, so that I can handle them appropriately.

Unfortunately no mater what I do I can't catch the exceptions, I tried java.lang.Exception's, Throwable's, Exception's and yet no luck.

from java.lang import Thread
from java.util.concurrent import Executors
from java.lang import Throwable
import java.lang.Exception

errors = 0

def myFunction():
	raise Throwable("I errored")
	
class MyUncaughtExceptionHandler(Thread.UncaughtExceptionHandler):
	def uncaughtException(self, t, e):
		print ("Thread %s threw exception %s" % (t.getName(), e))
		global errors
		errors += 1

class MyThread(Thread):
	def __init__(self, func):
		super(MyThread, self).__init__()
		self.setUncaughtExceptionHandler = MyUncaughtExceptionHandler()
		self.func = func

	def run(self):
		self.func()

def wait_for_async_execution(func, iterations, max_thread=-1, timeout_seconds=10):
	if max_thread == -1:
		max_thread = iterations
	
	executor = Executors.newFixedThreadPool(max_thread)
	for i in range(iterations):
		executor.submit(MyThread(func))

	executor.shutdown()
	executor.awaitTermination(timeout_seconds, java.util.concurrent.TimeUnit.SECONDS)

if __name__ == "__main__":
	wait_for_async_execution(myFunction, 5)
	print("Errors: %d" % errors)

Even Github copilot has not mentioned to save me yet...

I appreciate any help.

Your custom thread subclass is irrelevant here and the only reason it runs when you submit to the executor is because Thread implements Runnable.

Instead, you should be creating a ThreadFactory, which upon creating a Thread sets the exception handler, and then build an Executor that uses that ThreadFactory.

I had actually just started looking into the Factory when you said that, good call.

Here is something that works for any interested, still could use some cleanup though.


from java.lang import Thread
from java.lang import Runnable
from java.util.concurrent import Executors
from java.util.concurrent import ThreadFactory
from java.util.concurrent import TimeUnit

import time, random

def myFunction():
	wait_time = random.randint(0, 1)
	print ("Thread waiting for %d seconds" % wait_time)
	if wait_time == 1:
		print("I errored")
		raise Exception("I errored")
	
	time.sleep(float(wait_time * 0.5))

class FunctionWrapper(Runnable):
	def __init__(self, func):
		self.func = func

	def run(self):
		self.func()

class AsyncThreadFactory(ThreadFactory):
	# Set the uncaught exception handler for all threads created by this factory
	def newThread(self, runnable):
		thread = Thread(runnable)
		thread.setUncaughtExceptionHandler(MyUncaughtExceptionHandler())
		return thread

class MyUncaughtExceptionHandler(Thread.UncaughtExceptionHandler):
	def uncaughtException(self, t, e):
		print ("Thread %s threw exception %s" % (t.getName(), e))

def wait_for_async_execution(func, iterations, max_thread=-1, timeout_seconds=10):
	if max_thread == -1:
		max_thread = iterations
	
	executor = Executors.newFixedThreadPool(max_thread, AsyncThreadFactory())

	for i in range(iterations):
		executor.execute(FunctionWrapper(func))
		
	executor.shutdown()
	executor.awaitTermination(timeout_seconds, TimeUnit.SECONDS)

if __name__ == "__main__":
	wait_for_async_execution(myFunction, 5)
2 Likes

Glad I successfully nerd-sniped you :slight_smile:

You may not even need the uncaught exception handlers, if you use executors.submit:

	futures = [executor.submit(FunctionWrapper(func)) for _ in range(iterations)]
		
	executor.shutdown()
	executor.awaitTermination(timeout_seconds, TimeUnit.SECONDS)
	
	for future in futures:
		print future
1 Like

This was the a big reason I made that code public, having code critiqued in the public eye makes everyone better!

Out of curiosity, would it be possible in scripting to set the default uncaught exception handler for Perspective worker threads in a similar way to this?

That way if something fails scripting wise and it was uncaught, you could throw something on the screen to indicate an error? Instead of the default where it just silently fails into the gateway logs?