Python macro - Calc - Stuck with getCellByPosition and threads

Hi,

As I’m far from a Python expert, I need your help for probably a stupid race condition problem with a Python macro in LibreOffice Calc.

In short, here’s the piece of code:

# coding: utf-8
from __future__ import unicode_literals
from threading import Thread

def log():
    sheet = XSCRIPTCONTEXT.getDocument().getCurrentController().getActiveSheet()
    sheet.getCellByPosition(0, 0)

def go(*args):
    threads = []
    for i in range(0, 10):
        thread = Thread(target = log)
        threads.append(thread)

    for thread in threads:
        thread.start()
    
    for thread in threads:
        thread.join()

When I execute the go function, the script hangs on the log function and LibreOffice Calc just freezes: it must be killed.

I also tried to take advantage of a mutex (Lock) in the log function but I got the same result.

So my question is: what’s wrong with this code?

You’ll find attached to this post a .ods file as example: test_macro.ods

Many thanks for your help!

Flavien

+1 for a good example, easy to reproduce. Well formatted too.

The primary problem is not a race condition. The main thread must be unblocked so that LibreOffice can respond. Your code calls thread.join() which blocks the main thread until the other threads are finished, yet the other threads do not progress unless LibreOffice can respond. So it waits forever, or in other words, LibreOffice freezes.

Here is some code that works better.

def log(oDoc):
    time.sleep(1)
    oSheet = oDoc.getSheets().getByIndex(0)
    oCell = oSheet.getCellByPosition(0, 0)
    with Lock():
        with open(filepath, 'a') as f:
            f.write("{}\n".format(oCell.getString()))

def go(*args):
    oDoc = XSCRIPTCONTEXT.getDocument()
    for _ in range(10):
        thread = Thread(target = log, args = (oDoc,))
        thread.start()

If you need to run commands after the threads are done, then you could start a different thread that waits for something and then executes those commands.

Related: multithreading - Python UNO and Threads - Stack Overflow

Great! Thank you very much.
Indeed, as explained here, the main problem is that the macro somehow blocks the main thread (the UI).
Thus, one solution as proposed by Jim K. would be to get rid of the thread.join(). However, in my particular case I need to wait for the threads to complete, hence the thread.join(), in order to execute some additional computation.
So the solution is finally rather simple: create a separate thread for the macro execution.

Hi,

The solution that solved my problem is to create a separate thread as soon as the macro is called as follows:

# coding: utf-8
from __future__ import unicode_literals
from threading import Thread

def bar():
    sheet = XSCRIPTCONTEXT.getDocument().getCurrentController().getActiveSheet()
    sheet.getCellByPosition(0, 0)

def foo():
    threads = []
    for i in range(0, 10):
        thread = Thread(target = bar)
        threads.append(thread)

    for thread in threads:
        thread.start()

    for thread in threads:
        thread.join()

    print('All the threads completed')

    # Now, do whatever you need to do after the threads completion

def run_macro(*args):
    t = Thread(target = foo)
    t.start()

You can find here (test_macro.ods) a kind of PoC file with additional randomness for demo.

Again, many thanks to JimK for your help!

Looks like a good solution. This answer can be marked as accepted after waiting for a week.