Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Async refactor #141

Merged
merged 33 commits into from Aug 27, 2013
Merged

Async refactor #141

merged 33 commits into from Aug 27, 2013

Conversation

carlosmunoz
Copy link
Member

This is a refactor of the background process (now async task) mini-framework inside Zanata. The new changes make the code less verbose and it uses more JVM and Guava constructs like Future and ListenableFuture.

Some things can be expanded upon later, like adding a way to cancel a task even if it means interrupting it. The interface allows for it, but the implementation currently doesn't

@ghost ghost assigned davidmason Aug 23, 2013
@@ -47,6 +45,8 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file needs imports organized - several unused imports here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any unused imports in this file. Are you sure you commented on the right one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely looks like the right file to me. Eclipse is giving me unused import warnings for:

import com.google.common.collect.MapMaker;
...
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right. For some reason Idea was not showing me the unused imports.

{
<V, H extends AsyncTaskHandle<V>> String startTask(AsyncTask<V, H> task);

<V, H extends AsyncTaskHandle<V>> void startTask(AsyncTask<V, H> task, Serializable key);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would I start a task with a key vs. without a key? I think some javadoc would be handy to provide the information.

Only for Document and Iteration so far, but could be extended to others i nthe future.
@Scope(ScopeType.STATELESS)
@AutoCreate
@Slf4j
public class AsynchronousExecutor
public class AsynchronousTaskExecutor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this class is only intended for use through TaskExecutor, it could be made package-protected to reduce the possibility of inappropriate use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be package protected because Seam needs component classes to be public. And this needs to be a component class in order to take advantage of the @Asynchronous annotation. That's the reason for adding the comment as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable.

What is the reason not to use it directly? Might be handy to mention that in the javadoc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because we need an executor that is able to return the handle
after starting the process in the background. Methods marked as
asynchronous should not return anything, so we need that extra level of
nesting to execute some things synchronously (like returning the
handle) and others asynchronously (the task itself). Maybe in the
future we can integrate this class more closely with
java.util.concurrent.ExecutorService, but for the time being it's not
really needed. I'll add some brief javadoc for this.

Carlos A. Munoz
Senior Software Engineer
Engineering - Internationalization
Red Hat

On Mon 26 Aug 2013 03:33:26 PM EST, David Mason wrote:

In
zanata-war/src/main/java/org/zanata/async/AsynchronousTaskExecutor.java:

@scope(ScopeType.STATELESS)
@autocreate
@slf4j
-public class AsynchronousExecutor
+public class AsynchronousTaskExecutor

Sounds reasonable.

What is the reason not to use it directly? Might be handy to mention
that in the javadoc.


Reply to this email directly or view it on GitHub
https://github.com/zanata/zanata-server/pull/141/files#r5971423.

}

@Override
public String call() throws Exception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method could probably be split into several smaller methods for readability - not a blocker for this merge, but worth noting somewhere.

@davidmason
Copy link
Contributor

It looks like there are several places where async tasks can be running but might not have a process handle available. Is this expected much under normal circumstances? If it isn't normal, we could probably simplify a lot of code by failing early if a handle isn't available.

{
// Cancel any other processes
this.zipFilePrepHandle.stop();
this.zipFilePrepHandle.cancel(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel(false) is a little confusing at call sites like this. We can make our AsyncTaskHandle friendlier than AbstractFuture by adding a methods like:

public boolean cancelIfNotStarted()
{
   return cancel(false);
}

public boolean forceCancel()
{
   return cancel(true);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure abut this one. First, the boolean passed to the method is indicating whether the thread running the task may be interrupted while running or not (cancel(false) doesn't really mean cancelling only if not started; some tasks understand the concept of being cancelled and will stop processing when this signal is received). Currently we don't support that thread interruption, so the boolean is meaningless really. But I'd like to leave the option open to implement such a feature, where we are able to asynchronously run tasks that were not meant to be asynchronous (an thus have no concept of "stopping"), but at the same time being able to forcefully cancel them. this might require some thought and prototyping (and might not even be possible with Seam 2), and since it wasn't really an option in the previous setup, I didn't bother going into it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comment is just about readability at the call site - when someone reads a line that says:

handle.cancel(false);

they can only guess at what that might mean (and will likely guess wrong) - basically it forces them to look up the javadoc or find the implementation, which is disruptive to quickly getting an understanding of what the code does.

My suggestion here is just to wrap the parameterized method in a couple of more readable methods, so that all code that cancels an async process is more readable, so readers don't have to do a double-take and investigate when they're just trying to get a quick overview of what the code is doing, e.g. you hit a line that says:

handle.cancel()

or

handle.forceCancel()

or something else that makes sense right away.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have mixed feelings about this. One the one hand the methods are more specific, but on the other now we have three methods that do a cancel operation, most of them being logically equivalent. So, if avoiding a javadoc lookup is what you're after, you've just traded the lookup when trying to understand the code, for a lookup when trying to actually cancel an operation (and deciding which of the three cancel methods you want to use).

But it's probably not worth dragging this discussion any longer, so I'll cave and just add the methods :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, ideally the method that takes the boolean would not be there, but that's not how Java does things. Better still would be if we could do:

handle.cancel(interruptIfRunning=true)

but this isn't python.

@carlosmunoz
Copy link
Member Author

Not sure what you mean here. The whole idea is structured so that
running an async task gives you a handle always. You may choose to
ignore the handle, or even the task implementation may ignore the handle
(there's nothing really to prevent you from doing that), but not sure
which places you are talking about where async tasks are running without
a process handle being available.

Carlos A. Munoz
Senior Software Engineer
Engineering - Internationalization
Red Hat

On 08/26/2013 06:47 PM, David Mason wrote:

It looks like there are several places where async tasks can be running
but might not have a process handle available. Is this expected much
under normal circumstances? If it isn't normal, we could probably
simplify a lot of code by failing early if a handle isn't available.


Reply to this email directly or view it on GitHub
#141 (comment).

public void copyTransForDocument(HDocument document, HCopyTransOptions copyTransOpts)
{
Optional<CopyTransTaskHandle> asynchronousProcessHandle =
AsyncUtils.getEventAsyncHandle(CopyTransTaskHandle.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eclipse is saying this is unused.

@davidmason
Copy link
Contributor

@carlosmunoz actually a closer look at the optional process handles shows that there are only a few of them in CopyTransServiceImpl, where it looks up the handle using AsyncUtils and will increment progress or similar only if the handle is present. It seems odd that there would not be a handle for the running process, so I was wondering whether we would ever expect not to have a handle. I have a feeling it isn't particularly important.

@carlosmunoz
Copy link
Member Author

@davidmason The ideal (though admittedly probably not possible) would be to have any method being able to be called asynchronously or synchronously, with progress updates happening automatically. The best we have at the moment is to update progress optionally, so that nothing breaks when calling synchronously or asynchronously.

We would expect not to have a handle when the process is called synchronously, and that is why AsyncUtils returns an Optional. We can find better ways of doing this, but I just had to draw a line on how much to implement for this pull request.

@davidmason
Copy link
Contributor

@carlosmunoz ok, that makes sense if it can be run synchronously as well.

For any operation that can report progress, reports could just be ignored when it is running synchronously. As for automatic progress updates, I think that will only ever be possible in cases where we can reasonably define a set of tasks that the async task runner could iterate over and run each - probably impossible for some operations and certainly too much to do any time soon.

Carlos A. Munoz added 5 commits August 27, 2013 11:45
Still not sure about this duplication of cancel methods, but lets see how it goes.
…nal now.

Initally though about throwing an exception, but really the estimated time might be asked for even after the process has started, so it's not really suitable to throw an exception in that case. Returning an optional seems like the next best possibility.
…efactor

Conflicts:
	zanata-war/src/main/java/org/zanata/action/TranslationMemoryAction.java
	zanata-war/src/main/java/org/zanata/service/ProcessManagerService.java
	zanata-war/src/main/java/org/zanata/service/impl/ProcessManagerServiceImpl.java
Make sure the translation memory uses of the async classes is working properly.
@davidmason
Copy link
Contributor

👍

davidmason added a commit that referenced this pull request Aug 27, 2013
@davidmason davidmason merged commit e875eea into integration/master Aug 27, 2013
@davidmason davidmason deleted the async-refactor branch August 27, 2013 06:21
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
2 participants