Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python thread accesses Java instance in another thread #588

Open
akiou opened this issue Mar 30, 2021 · 6 comments
Open

Python thread accesses Java instance in another thread #588

akiou opened this issue Mar 30, 2021 · 6 comments

Comments

@akiou
Copy link

akiou commented Mar 30, 2021

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.

@laurachiticariu
Copy link

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

@akiou
Copy link
Author

akiou commented Jul 1, 2021

Hi team, do you have any updates about this?

@Enolerobotti
Copy link

Enolerobotti commented May 28, 2022

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
Copy link

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()

@TheCreatorAMA
Copy link

Has anyone found any solutions to this problem?

@akiou
Copy link
Author

akiou commented Sep 25, 2023

@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, ...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants