Introduction
The goals of the threading solution research are:- To establish how threading currently works in OpenOffice
- To investigate issues related to running multiple scripts simultaneously in OpenOffice
- To suggest how IDEs would be able to attach to OpenOffice scripts
Contents
- Current Star Basic script threading behaviour
- Current Ms Basic script threading behaviour
- Event Handling
- Options for script threading behaviour
- Issues with allowing one script per document
- Configuration of threading behaviour
- Identifying script threads in a debugger
- SolarMutex
Current StarBasic script threading behaviour
Run the macro from the "Macro" dialogbox.
- No frozen document you can work on any documents
- Macro running in background
- No macro can be run
- Active document is frozen
- Other document are not frozen
- Can't run any macro on other document while the macro is still running even from the "Macro" dialogbox.
- If you close the dialog box, the macro is resumed
- If you decide to edit a macro, the macro is resumed and you can stop the macro from the BASIC editor
- If you switch to the BASIC editor and click on the stop button, the macro will be stop but nothing in the UI will be updated until the "Macro" dialogbox is closed (this seems to be a bug).
Current Ms Basic script threading behaviour
Run the macro from the "Macro" dialogbox.
- All document are frozen
- All document are frozen
- All document are frozen
- All word document are frozen
- Excel is not frozen, macro is running and changing value and you can change values.
Event Handling
The OpenOffice process has multiple threads. Any thread that calls Application::Yield or Application::Reschedule dispatches GUI events. Dispatching GUI events requires the SolarMutex, so only one thread at a time can dispatch GUI events. In practice the "main" OpenOffice thread is the thread that dispatches most GUI events.
Events can be either synchronous or asynchronous.
Synchronous events are handled immediately, that means that the functionality bound to the event is invoked immediately from the call stack of the thread where the event occurred. The thread is therefore blocked until the event has been handled.
Asynchronous events are placed in a message queue by the thread handling the event, and the thread is not blocked, it continues executing. The message queue is serviced by any thread which calls Application::Yield or Application::Reschedule.
Options for script threading behaviour
There are two possible threading models that we could use:
- One script per OpenOffice process - this is the threading model currently used by OpenOffice.
- One script per OpenOffice document - this would allow more than one script to be run at the same time, but only one script could be run per document at a time. The thread running the script would have to grab a document lock before invoking the script and release it when the script has completed.
The best solution is to implement option 2, and add the possibility to configure the threading behaviour so that it acts like option 1 if the user wishes to use this behaviour (see Configuration section below).
So, how do we implement option 2? From discussions with Mathias Bauer and Joerg Budischweski they feel that OpenOffice should manage the spawning of the threads which would invoke the scripts. In the case of synchronous events the thread handling the event would spawn a thread in which it would then invoke the script. In the case of asynchronous threads the thread which had grabbed the event off the message queue would spawn a new thread when it wanted to invoke a script. In other words the responsibility for managing the per document script threads would lie with OpenOffice, not the scripting framework.
Changes needed:
- The thread calling XFunction.invoke() (main thread for synchronous events, message queue thread for asynchronous events) needs to spawn a new thread and call XFunction.invoke() from there.
- The document for which the XFunction is being invoked needs to be locked for the duration of XFunction.invoke() (with a documentmutex of some sort) so other XFunctions cannot be invoked for that document. The most difficult thing is that also every view that tries to modify a document must stick to this rule
See also: "Threading model for an office scripting framework" discussion on openoffice.udk.dev newsgroup
Issues with allowing one script per document
Because of problems with thread safety in the OpenOffice API it is currently unsafe to allow more than one thread to make API calls at the same time. For this reason there is a global SolarMutex which thread unsafe API implementations automatically lock before executing any functionality, and unlock before returning. The thread calling the OpenOffice API is not aware of this SolarMutex. While the SolarMutex is grabbed, OpenOffice API calls from other threads will block until the SolarMutex becomes free. The OpenOffice UI will be frozen while the SolarMutex is locked. If a script calls a OpenOffice API, the solarmutex will be grabbed until the API call returns which would temporarily freeze all document windows. These freezes may be an unacceptable annoyance to users and they may prefer that the UI be completely frozen for the duration of script execution rather than having it freeze unexpectedly.
See also: "Office API and thread safety" discussion on openoffice.framework.dev newsgroup
Configuration of threading behaviour
The threading behaviour of Office should be configurable so that the user can switch on/off the one script per document behaviour as they wish. A radio box should be added to the Scripting pane of the Office options dialog with the options:
Allow only one script to be run by OpenOffice at a time.
Allow one script per document to be run by OpenOffice at a time
Identifying script threads in a debugger
The issue here is how we can identify which thread in a OpenOffice process is running the script we are interested in debugging. This is possible in java using the java.lang.Thread.setName() method. The pairing of script name and document name should be sufficient to uniquely identify the appropriate thread.
SolarMutex
A problem could arise if a thread that does not possess the SolarMutex
tries to execute a script synchronously, because any API call in the script
can possibly acquire the SolarMutex. This may cause a deadlock, f.e. if the
thread possessing the SolarMutex waits for a resource that is locked by the
thread that tries to execute a script synchronously. So we could add some
code here, that denies script execution, if it should be done synchronously
and the SolarMutex is locked. It's better to treat this as an error than taking
the risk of getting a deadlock.
======
suffice to say that for synchronous executions the thread owns the SolarMutex,
it doesn't need to be the "main" thread.
======
Every not thread safe implementation of our API will acquire the SolarMutex
in any interface call before anything will be done inside the method. So the
function object doesn't need to care for that
======
the SolarMutex should be locked only inside the API calls (it will be anyway),
not by the function object (it mustn't, because it doesn't know if it is called
synchronously or not!), the event handler or anybody else. A big advantage
of this is, that the SolarMutex will be freed between two API calls, giving
the message queue a chance to allow for a minimum responsiveness of the office
process. The script then will work in parallel to the event queue. Only
for synchronous execution we should try to (!) acquire the SolarMutex before
executing the script as described above. If the try fails, script execution
should be denied. I consider this to be a rare case, currently I can't imagine
that any event that may cause a script execution will occur in a thread without
the SolarMutex. But it's good to handle this case in an appropriate way.
======