Post

2 followers Follow
0
Avatar

How to wait for a documentOperation to finish

I am trying to create a plugin that uses the BLAST plugin (getDocumentOperation("sequenceSearch")), but I would like to further manipulate the input document after the BLAST operation is done.  However, if I try to, for example, delete the input document, I get a null pointer exception.  Is there any way to monitor the progress of the BLAST documentOperation and wait until it is done before proceeding?

Eric Van Name

Please sign in to leave a comment.

9 comments

0
Avatar

OK, I was able to get to the result I am trying to achieve, but I'm thinking there has to be a better way.  First I used the waitForSearchIndexingToComplete() method to make sure everything was done before moving on.  That worked, but I don't think it is a good way to go because if somebody recently added a bunch of documents then my plugin won't finish until all of the indexing is complete.

The next thing I tried was implementing a GeneiousServiceListener and using a CountDownLatch to monitor the number of results appearing in my folder.  Unfortunately, the childService for the result gets added before the BLAST operation is done with the original document, so I couldn't use that.  However, I noticed that the childService icon changes three times (four if there are no results, but then a dialog box appears, which effectively makes the plugin wait).  So, now I have a CountDownLatch, started with a count of three times the number of results I expect, counting down every time iconsChanged() is called. and wait for that before moving on.

This is working, but it just seems like a horrible sloppy hack and I was hoping you might be able to suggest a better strategy?  Thanks!

Eric Van Name 0 votes
Comment actions Permalink
0
Avatar

Unlike most operations, the BLAST operation runs in a separate thread and returns from performOperation straight away. We have a method that makes it execute in the same thread and only return once the search is complete but it is not available from the public API. Here is how you can use reflection to call it instead.

try {
  Class<?> privateUtilities = Class.forName("com.biomatters.geneious.common.privateapi.a");
  Method setRunningFromScript = privateUtilities.getDeclaredMethod("c", Boolean.TYPE);
  setRunningFromScript.setAccessible(true);
  setRunningFromScript.invoke(null, true); // tell Geneious we are running from a script
} catch (Exception e) {
  throw new RuntimeException(e);
}

//call blast here, when performOperation returns you know the search is finished.

try {
  Class<?> privateUtilities = Class.forName("com.biomatters.geneious.common.privateapi.a");
  Method setRunningFromScript = privateUtilities.getDeclaredMethod("c", Boolean.TYPE);
  setRunningFromScript.setAccessible(true);
  setRunningFromScript.invoke(null, false); // tell Geneious we are no longer running from a script
} catch (Exception e) {
  throw new RuntimeException(e);
}

Richard Moir 0 votes
Comment actions Permalink
0
Avatar

Thanks, that's very useful!  So I want to make sure I understand how this is working, because at first glance this looks like it is more general than just the BLAST operation.

When you invoke setRunningFromScript(true), does that restrict all of the following code to a single thread until setRunningFromScript(false) is called?  Or does it only apply to a subset of operations (for example, DocumentOperations and SequenceAnnotationGenerators), or even just specifically to the BLAST operation? 

Also, if this is a more general tool, is the single thread independent of the main awt thread?  In other words, if I call something that takes a while to run, will there be any problems with the gui becoming unresponsive?

Eric Van Name 0 votes
Comment actions Permalink
0
Avatar

It only applies to certain DocumentOperations and SequenceAnnotationGenerators. It is a flag which is voluntarily checked by operations when they want to do something that would be detrimental to scripting. The most common case is when an operation wants to pop up a dialog to inform the user of something during execution. Since this would pause the script and wait for input, it is suppressed when the flag is true.

The BLAST operation is the only one I'm aware of where this flag affects threading, in most other cases it just suppresses dialogs.

Assuming you are writing a DocumentOperation yourself, you generally don't have to worry about blocking AWT thread because performOperation is called on a background thread.

Richard Moir 0 votes
Comment actions Permalink
0
Avatar

Thanks for the clarification.  I guess I got overly hopeful because I already ran into the same issue with another part of my plugin.  I use the set up BLAST services operation to check if the user has the correct common BLAST folder, and if not, set it for them.  Unfortunately, that one also runs in its own thread, so if I call a BLAST search right after it will use the old BLAST folder location.  It isn't a big deal, though, because if I need to change the folder I cheat and pop a dialog box 'helpfully' informing the user about the change (and buying the plugin enough time to make the change before proceeding). ;)

Eric Van Name 0 votes
Comment actions Permalink
0
Avatar

Where we have a dependency like that on custom blast (eg. for Vector Trimming) we just tell the user to change it themselves then try again. It is less convenient for them but less likely to cause issues. It should be once-off anyway.

Richard Moir 0 votes
Comment actions Permalink
0
Avatar

OK, I finally got around to trying to implement your solution, but I am having some problems.  At first I just added the two try blocks around the line where I call the blast operation, but got a progressListener error:

"Exception: java.lang.AssertionError: Progress shouldn't go backwards. Went from 0.95 to 0.0 on progress listener with weights=[0.95, 0.05]"

I was using the same progressListener that gets passed into my performOperation method for several things, including the BLAST operation as well as some document copying/deleting.  So I tried to (temporarily) get around that by using ProgressListener.EMPTY for the document copying/deleting, which got rid of that error.  However, I don't fully understand this, so any explanation would be welcome.

In any case, although I was able to get rid of the progressListener error, once I add those try blocks to my plugin, the blast results stop appearing.  Any suggestions?

Eric Van Name 0 votes
Comment actions Permalink
0
Avatar

Reusing the ProgressListener is the correct thing to do but you have to wrap it in a CompositeProgressListener to correctly split the operation in to subtasks. This tells Geneious up front how many sub tasks there are and how big each one is so it can provide the user with sensible progress. Hopefully that makes sense.

Any chance you could send me the code so I can take a look? richard at biomatters dot com.

Richard Moir 0 votes
Comment actions Permalink
0
Avatar

Our findings for the record:

In some cases the progress reported by custom blast can go backwards slightly which will cause an assertion error when running it from a workflow. Since it is an assertion error, only developers will see the crash when running from the IDE. A low priority bug has been logged internally for fixing this later.

Richard Moir 0 votes
Comment actions Permalink