pyjnius icon indicating copy to clipboard operation
pyjnius copied to clipboard

Python thread accesses Java instance in another thread

Open akiou opened this issue 3 years ago • 6 comments

Environment

  • OS: macOS Big Sur 11.1
  • PyJnius version: 1.3.0
  • Python version: 3.7.7
  • Java version: AdoptOpenJDK 1.8.0_265-b01
  • Maven version: 3.6.3

Expected behavior

In the case of multithreading of Python, a Java instance created in a Python thread should not be accessed from another Python thread.

Observed behavior

A Java instance created in a Python thread is accessed from another Python thread.

How to reproduce

sample code: https://github.com/akiou/pyjnius_multithreading

In the sample code, a Python process creates multiple threads, and each thread creates an Java instance and invokes its methods. Although a python thread must create and use only one java instance, the validate() method is occasionally invoked from different python thread.

You need to execute the python script test.py in the sample code multiple times to reproduce this error because I don't know when this error can be occurred.

akiou avatar Mar 30 '21 04:03 akiou

Hello pyjnius maintainers, I am also running into this issue. Would anyone have a chance to have a look ?

laurachiticariu avatar Apr 22 '21 01:04 laurachiticariu

Hi team, do you have any updates about this?

akiou avatar Jul 01 '21 00:07 akiou

Hi team, I've also faced this issue. Here is the minimal reproducible example,

import jnius
import threading


def do_job(tid):
    cls = jnius.autoclass('java.lang.String')
    print(f"thread: {tid}, Id: {cls} | {id(cls)}")


def main():
    jnius.autoclass('java.lang.String')  # comment out this line to make ids different

    threads = []
    for i in range(10):
        t = threading.Thread(target=do_job, args=(i,))
        t.start()
        threads.append(t)
    for t in threads:
        t.join()


if __name__ == '__main__':
    main()

If you comment out the marked line, the code becomes partially* thread safe. The ids will be different. However, if you first instantiated a Java class in the main thread, all the threads reuse this instance. Thus, the ids will be the same.

  • UPDATE: not completely but rather partially thread safe when the number of threads is huge. Try 100 in the example above and compute id frequencies.

Enolerobotti avatar May 28 '22 10:05 Enolerobotti

More complicated snippet using ArrayList

import jnius
import threading
from queue import Queue


def do_job(tid, q):
    ArrayList = jnius.autoclass('java.util.ArrayList')
    arraylist = ArrayList()
    arraylist.add(tid)
    arraylist.set(0, arraylist.get(0))
    q.put((tid, arraylist))


def do_job_passed(tid, q):
    ArrayList = jnius.autoclass('java.util.ArrayList')
    arraylist = ArrayList()
    arraylist.add(tid)
    a = arraylist.get(0)  # use a trick with assigning an explicit memory address
    arraylist.set(0, a)
    q.put((tid, arraylist))


def main():
    jnius.autoclass('java.util.ArrayList') # comment out this line to fix mutability or use do_job_passed
    queue = Queue()
    threads = []
    ref_ints = list(range(100))
    ints = [None] * 100
    for i in ref_ints:
        t = threading.Thread(target=do_job, args=(i, queue))
        t.start()
        threads.append(t)
    for t in threads:
        tid, arraylist = queue.get()
        t.join()
        ints[tid] = arraylist.get(0)
    if ints != ref_ints:
        print(ints)
        print(ref_ints)
        for j, (i, ri) in enumerate(zip(ints, ref_ints)):
            if i != ri:
                print(f"index {j}, expected {ri}, actual {i}")
        raise ValueError("ints != ref_ints")


if __name__ == '__main__':
    main()

Enolerobotti avatar May 30 '22 12:05 Enolerobotti

Has anyone found any solutions to this problem?

TheCreatorAMA avatar Sep 24 '23 17:09 TheCreatorAMA

@TheCreatorAMA I found that this issue may not be occured when static method invocation. As a temporary workaround, you should define a wrapper static method in Java side as follows:

class ClassA {
    public Object b(Object arg1, Object arg2, ...) {
        return ...;
    }

    public static Obect b(ClassA instance, Object arg1, Object arg2, ...) {
        return instance.b(arg1, arg2, ...);
    }
}

Then, you should invoke ClassA.b(instance, arg1, arg2, ...) instead of instance.b(arg1, arg2, ...)

akiou avatar Sep 25 '23 13:09 akiou