Re: [Bug# 228886] Multiple Glassfish threads hanging in memory

Tomas Kraus <[email protected]> Tue, 21 May 2013 16:07:21 +0200
Newsgroups gmane.comp.java.netbeans.reviewers
Message-ID <[email protected]>
This is a multi-part message in MIME format.
--------------010805040901090205040105
Content-Type: multipart/alternative;
 boundary="------------080002080101080309030708"


--------------080002080101080309030708
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Attaching hg diff -c 280234be9cb0 > sdk-280234be9cb0.diff

On 05/21/2013 03:58 PM, Petr Jiricka wrote:
> On May 21, 2013, at 1:42 PM, Tomas Kraus wrote:
>
>> *Bug 228886* <https://netbeans.org/bugzilla/show_bug.cgi?id=228886> 
>> -Multiple Glassfish threads hanging in memory
>>
>> Zombie thread is left after GlassFish Log Reader thread has been 
>> interrupted in build task.
>> - Start NetBeans
>> - Start VisualVM and open NetBeans, threads table, sort by Thread
>> - Create sample project ServletStateless
>> - Start GlassFish server in debug mode
>> - Undeploy all projects
>> Repeat the following cycle
>> - Run project ServletStateless
>>    creates one thread FetchLogPiped
>> - Clean and build project
>>    creates one thread FetchLogPiped.
>>
>> So if you run this cyle 3 times you should have 6 FetchLogPiped threads.
>> Fix in GF plugin was made in 4 steps (253015:70350e3b09dc, 
>> 253069:422df5694675, 253094:1ad1e811d955 and 253176:e0be6fab29ed), 
>> combined diff is attached as 
>> https://netbeans.org/bugzilla/attachment.cgi?id=134564&action=diff
>>
>> I made single thread pool (ExecutorService) to be shared for all 
>> GlassFish Log Reader threads. Threads are run under top level 
>> ThreadGroup to not be interrupted by build task. Threads have 
>> setDaemon(true) set to avoid them being NB exit blockers.
>>
>> GlassFish Tooling Library changeset is 502:280234be9cb0.
>
> Tomas, please attach a diff for this.
>
> Thanks,
> Petr
>
>
>>
>> It allows to pass external ExecutorService into GlassFish Log Reader 
>> thread starting code.
>>
>> Code went trough 2 reviews - Petr Hejl and Peter Benedikovic. It was 
>> cleaned up thanks to Petr Hejl notes and now I believe it's safe 
>> enough to be pushed in to 7.3.1 release branch. QA testing was done 
>> by Jiri Skrivanek. More details are in the bug.
>>
>


--------------080002080101080309030708
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

<html>
  <head>
    <meta content="text/html; charset=ISO-8859-1"
      http-equiv="Content-Type">
  </head>
  <body bgcolor="#FFFFFF" text="#000000">
    Attaching hg diff -c 280234be9cb0 &gt; sdk-280234be9cb0.diff<br>
    <br>
    On 05/21/2013 03:58 PM, Petr Jiricka wrote:
    <blockquote
      cite="mid:[email protected]"
      type="cite">
      <div>
        <div>On May 21, 2013, at 1:42 PM, Tomas Kraus wrote:</div>
        <br class="Apple-interchange-newline">
        <blockquote type="cite">
          <meta http-equiv="content-type" content="text/html;
            charset=ISO-8859-1">
          <div bgcolor="#FFFFFF" text="#000000"> <a
              moz-do-not-send="true"
              href="https://netbeans.org/bugzilla/show_bug.cgi?id=228886"><b>Bug&nbsp;228886</b></a>
            -<span id="summary_alias_container"> <span
                id="short_desc_nonedit_display">Multiple Glassfish
                threads hanging in memory<br>
                <br>
                Zombie thread is left after GlassFish Log Reader thread
                has been interrupted in build task.<br>
              </span></span>
            <pre class="bz_comment_text">- Start NetBeans
- Start VisualVM and open NetBeans, threads table, sort by Thread
- Create sample project ServletStateless
- Start GlassFish server in debug mode
- Undeploy all projects
Repeat the following cycle
- Run project ServletStateless
  creates one thread FetchLogPiped
- Clean and build project
  creates one thread FetchLogPiped.

So if you run this cyle 3 times you should have 6 FetchLogPiped threads.</pre>
            Fix in GF plugin was made in 4 steps (253015:70350e3b09dc,
            253069:422df5694675, 253094:1ad1e811d955 and
            253176:e0be6fab29ed), combined diff is attached as <a
              moz-do-not-send="true" class="moz-txt-link-freetext"
href="https://netbeans.org/bugzilla/attachment.cgi?id=134564&amp;action=diff">https://netbeans.org/bugzilla/attachment.cgi?id=134564&amp;action=diff</a><br>
            <br>
            I made single thread pool (ExecutorService) to be shared for
            all <span id="summary_alias_container"><span
                id="short_desc_nonedit_display">GlassFish Log Reader </span></span>threads.
            Threads are run under top level ThreadGroup to not be
            interrupted by build task. Threads have setDaemon(true) set
            to avoid them being NB exit blockers.<br>
            <br>
            GlassFish Tooling Library changeset is 502:280234be9cb0.<br>
          </div>
        </blockquote>
        <div><br>
        </div>
        <div>Tomas, please attach a diff for this.</div>
        <div><br>
        </div>
        <div>Thanks,</div>
        <div>Petr</div>
        <div><br>
        </div>
        <br>
        <blockquote type="cite">
          <div bgcolor="#FFFFFF" text="#000000"> <br>
            It allows to pass external ExecutorService into <span
              id="summary_alias_container"><span
                id="short_desc_nonedit_display">GlassFish Log Reader </span></span>thread
            starting code.<br>
            <br>
            Code went trough 2 reviews - Petr Hejl and Peter
            Benedikovic. It was cleaned up thanks to Petr Hejl notes and
            now I believe it's safe enough to be pushed in to 7.3.1
            release branch. QA testing was done by Jiri Skrivanek. More
            details are in the bug.<br>
            <br>
          </div>
        </blockquote>
      </div>
      <br>
    </blockquote>
    <br>
  </body>
</html>

--------------080002080101080309030708--

--------------010805040901090205040105
Content-Type: text/x-diff;
 name="sdk-280234be9cb0.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="sdk-280234be9cb0.diff"

diff -r 74660966b1bf -r 280234be9cb0 sdk/src/main/java/org/glassfish/tools/ide/admin/ServerAdmin.java
--- a/sdk/src/main/java/org/glassfish/tools/ide/admin/ServerAdmin.java	Mon Apr 29 14:14:57 2013 +0200
+++ b/sdk/src/main/java/org/glassfish/tools/ide/admin/ServerAdmin.java	Tue May 14 16:27:01 2013 +0200
@@ -114,9 +114,10 @@
      * <p>
      * This allows to execute tasks in parallel using provided executor.
      * <p/>
-     * @param srv Target GlassFish server.
-     * @param cmd Server administration command to me executed.
-     * @param ide IDE Context object.
+     * @param executor Executor service used to start task.
+     * @param srv      Target GlassFish server.
+     * @param cmd      Server administration command to me executed.
+     * @param ide      IDE Context object.
      */
     public static <E extends Result> Future<E> exec(ExecutorService executor,
             GlassFishServer srv, Command cmd, IdeContext ide) {
@@ -128,9 +129,10 @@
     /**
      * Execute remote administration command on GlassFish server.
      * <p>
-     * @param srv Target GlassFish server.
-     * @param cmd Server administration command to me executed.
-     * @param ide IDE Context object.
+     * @param executor Executor service used to start task.
+     * @param srv      Target GlassFish server.
+     * @param cmd      Server administration command to me executed.
+     * @param ide      IDE Context object.
      * @param listeners Listeners that are called when command execution status changes.
      */
     public static <E extends Result> Future<E> exec(ExecutorService executor,
diff -r 74660966b1bf -r 280234be9cb0 sdk/src/main/java/org/glassfish/tools/ide/server/FetchLog.java
--- a/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLog.java	Mon Apr 29 14:14:57 2013 +0200
+++ b/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLog.java	Tue May 14 16:27:01 2013 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 1997-2012 Oracle and/or its affiliates. All rights reserved.
+ * Copyright 1997-2013 Oracle and/or its affiliates. All rights reserved.
  *
  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  * Other names may be trademarks of their respective owners.
@@ -55,51 +55,6 @@
 public abstract class FetchLog {
     
     ////////////////////////////////////////////////////////////////////////////
-    // Static methods                                                         //
-    ////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Constructs an instance of GlassFish server log fetcher depending
-     * on server being remote or local.
-     * <p/>
-     * Decision if server is local or remote depends on domains folder and
-     * domain name attributes stored in <code>GlassFishServer</code> object.
-     * <p/>
-     * @param server GlassFish server for fetching server log.
-     * @param skip   Skip to the end of the log file.
-     * @return Newly created <code>FetchLog</code> instance.
-     */
-    public static FetchLog create(GlassFishServer server, boolean skip) {
-        boolean isLocal = server.getDomainsFolder() != null
-                && server.getDomainName() != null;
-        FetchLog fetchLog = isLocal
-                ? new FetchLogLocal(server, skip)
-                : new FetchLogRemote(server, skip);
-        return fetchLog;
-    }
-
-    /**
-     * Constructs an instance of GlassFish server log fetcher depending
-     * on server being remote or local.
-     * <p/>
-     * Decision if server is local or remote depends on domains folder and
-     * domain name attributes stored in <code>GlassFishServer</code> object.
-     * Log file is passed whole as is without skipping to the end.
-     * <p/>
-     * @param server GlassFish server for fetching server log.
-     * @param skip   Skip to the end of the log file.
-     * @return Newly created <code>FetchLog</code> instance.
-     */
-    public static FetchLog create(GlassFishServer server) {
-        boolean isLocal = server.getDomainsFolder() != null
-                && server.getDomainName() != null;
-        FetchLog fetchLog = isLocal
-                ? new FetchLogLocal(server, false)
-                : new FetchLogRemote(server, false);
-        return fetchLog;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
     // Instance attributes                                                    //
     ////////////////////////////////////////////////////////////////////////////
 
diff -r 74660966b1bf -r 280234be9cb0 sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogEvent.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogEvent.java	Tue May 14 16:27:01 2013 +0200
@@ -0,0 +1,86 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2008 Sun Microsystems, Inc.
+ */
+package org.glassfish.tools.ide.server;
+
+import org.glassfish.tools.ide.admin.TaskState;
+
+/**
+ * Events in GlassFish log fetcher.
+ * <p/>
+ * @author Tomas Kraus
+ */
+public class FetchLogEvent {
+    
+    ////////////////////////////////////////////////////////////////////////////
+    // Instance attributes                                                    //
+    ////////////////////////////////////////////////////////////////////////////
+
+    /** Current log fetcher task state. */
+    private TaskState state;
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Constructors                                                           //
+    ////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Constructs an instance of event in GlassFish log fetcher.
+     * <p/>
+     * @param Current log fetcher task state.
+     */
+    FetchLogEvent(TaskState state) {
+        this.state = state;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Getters and Setters                                                    //
+    ////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Get current log fetcher task state.
+     * <p/>
+     * @rerurn Current log fetcher task state.
+     */
+    public TaskState getState() {
+        return state;
+    }
+
+}
diff -r 74660966b1bf -r 280234be9cb0 sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogEventListener.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogEventListener.java	Tue May 14 16:27:01 2013 +0200
@@ -0,0 +1,58 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2008 Sun Microsystems, Inc.
+ */
+package org.glassfish.tools.ide.server;
+
+/**
+ * Listens for events in GlassFish log fetcher.
+ * <p/>
+ * @author Tomas Kraus
+ */
+public interface FetchLogEventListener {
+
+    /**
+     * Notification method called when log fetcher state was changed.
+     * <p/>
+     * @param event GlassFish log fetcher state change event.
+     */
+    public void stateChanged(final FetchLogEvent event);
+
+}
diff -r 74660966b1bf -r 280234be9cb0 sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogLocal.java
--- a/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogLocal.java	Mon Apr 29 14:14:57 2013 +0200
+++ b/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogLocal.java	Tue May 14 16:27:01 2013 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 1997-2012 Oracle and/or its affiliates. All rights reserved.
+ * Copyright 1997-2013 Oracle and/or its affiliates. All rights reserved.
  *
  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  * Other names may be trademarks of their respective owners.
@@ -42,6 +42,7 @@
 package org.glassfish.tools.ide.server;
 
 import java.io.*;
+import java.util.concurrent.ExecutorService;
 import java.util.logging.Level;
 import org.glassfish.tools.ide.admin.TaskState;
 import org.glassfish.tools.ide.data.GlassFishServer;
@@ -72,10 +73,30 @@
      *               and <code>getDomainName</code> should not return null.
      * @param skip   Skip to the end of the log file.
      */
-    FetchLogLocal(GlassFishServer server, boolean skip) {
+    FetchLogLocal(final GlassFishServer server, final  boolean skip) {
         super(server, skip);
     }
 
+    /**
+     * Constructs an instance of GlassFish local server log fetcher with
+     * external {@see ExecutorService}.
+     * <p/>
+     * Super class constructor will call <code>initInputStream</code> method
+     * which initializes <code>InputStream</code> as
+     * <code>FileInputStream</code> before this constructor code is being
+     * executed.
+     * <p/>
+     * @param executor Executor service used to start task.
+     * @param server   GlassFish server for fetching local server log. Both
+     *                 <code>getDomainsFolder</code>
+     *                 and <code>getDomainName</code> should not return null.
+     * @param skip     Skip to the end of the log file.
+     */
+    FetchLogLocal(final ExecutorService executor, final GlassFishServer server,
+           final boolean skip) {
+        super(executor, server, skip);
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Methods                                                                //
     ////////////////////////////////////////////////////////////////////////////
@@ -135,13 +156,14 @@
     @SuppressWarnings("SleepWhileInLoop")
     @Override
     public TaskState call() {
+        notifyListeners(TaskState.RUNNING);
         InputStream fIn = initInputFile();
         byte[] buff = new byte[PIPE_BUFFER_SIZE];
         File logFile = ServerUtils.getServerLogFile(server);
         int inCount;
         long lastModified;
         if (fIn == null) {
-            return TaskState.FAILED;
+            return notifyListeners(TaskState.FAILED);
         }
         while (taksExecute) {
             try {
@@ -176,25 +198,26 @@
                 Logger.log(Level.INFO,
                         "Caught InterruptedException: " + ie.getMessage());
                 Thread.currentThread().interrupt();
-                return TaskState.COMPLETED;
+                return notifyListeners(TaskState.COMPLETED);
             } catch (InterruptedIOException ie) {
                 Logger.log(Level.INFO,
                         "Caught InterruptedIOException: " + ie.getMessage());
+                ie.printStackTrace();
                 Thread.currentThread().interrupt();
-                return TaskState.COMPLETED;
+                return notifyListeners(TaskState.COMPLETED);
             } catch (IOException ioe) {
                 if (taksExecute) {
                     Logger.log(Level.INFO, "Caught IOException: ", ioe);
-                    return TaskState.FAILED;
+                    return notifyListeners(TaskState.FAILED);
                 } else {
                     Logger.log(Level.INFO,
                             "Caught IOException: " + ioe.getMessage());
-                    return TaskState.COMPLETED;
+                    return notifyListeners(TaskState.COMPLETED);
                 }
             }
 
         }
-        return TaskState.COMPLETED;
+        return notifyListeners(TaskState.COMPLETED);
     }
 
 }
diff -r 74660966b1bf -r 280234be9cb0 sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogPiped.java
--- a/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogPiped.java	Mon Apr 29 14:14:57 2013 +0200
+++ b/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogPiped.java	Tue May 14 16:27:01 2013 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 1997-2012 Oracle and/or its affiliates. All rights reserved.
+ * Copyright 1997-2013 Oracle and/or its affiliates. All rights reserved.
  *
  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  * Other names may be trademarks of their respective owners.
@@ -50,6 +50,7 @@
 import org.glassfish.tools.ide.admin.TaskState;
 import org.glassfish.tools.ide.data.GlassFishServer;
 import org.glassfish.tools.ide.logging.Logger;
+import org.glassfish.tools.ide.utils.LinkedList;
 
 /**
  * Fetch GlassFish log from local or remote server.
@@ -72,6 +73,101 @@
     /** Log refresh delay in miliseconds. */
     static final int LOG_REFRESH_DELAY = 1000;
     
+    ////////////////////////////////////////////////////////////////////////////
+    // Static methods                                                         //
+    ////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Constructs an instance of GlassFish server log fetcher depending
+     * on server being remote or local.
+     * <p/>
+     * Decision if server is local or remote depends on domains folder and
+     * domain name attributes stored in <code>GlassFishServer</code> object.
+     * <p/>
+     * @param server GlassFish server for fetching server log.
+     * @param skip   Skip to the end of the log file.
+     * @return Newly created <code>FetchLog</code> instance.
+     */
+    public static FetchLogPiped create(final GlassFishServer server,
+            final boolean skip) {
+        boolean isLocal = server.getDomainsFolder() != null
+                && server.getDomainName() != null;
+        FetchLogPiped fetchLog = isLocal
+                ? new FetchLogLocal(server, skip)
+                : new FetchLogRemote(server, skip);
+        fetchLog.start();
+        return fetchLog;
+    }
+
+    /**
+     * Constructs an instance of GlassFish server log fetcher depending
+     * on server being remote or local.
+     * <p/>
+     * Decision if server is local or remote depends on domains folder and
+     * domain name attributes stored in <code>GlassFishServer</code> object.
+     * Log file is passed whole as is without skipping to the end.
+     * <p/>
+     * @param server GlassFish server for fetching server log.
+     * @param skip   Skip to the end of the log file.
+     * @return Newly created <code>FetchLog</code> instance.
+     */
+    public static FetchLogPiped create(final GlassFishServer server) {
+        boolean isLocal = server.getDomainsFolder() != null
+                && server.getDomainName() != null;
+        FetchLogPiped fetchLog = isLocal
+                ? new FetchLogLocal(server, false)
+                : new FetchLogRemote(server, false);
+        fetchLog.start();
+        return fetchLog;
+    }
+
+    /**
+     * Constructs an instance of GlassFish server log fetcher depending
+     * on server being remote or local  with external {@see ExecutorService}.
+     * <p/>
+     * Decision if server is local or remote depends on domains folder and
+     * domain name attributes stored in <code>GlassFishServer</code> object.
+     * <p/>
+     * @param executor Executor service used to start task.
+     * @param server   GlassFish server for fetching server log.
+     * @param skip     Skip to the end of the log file.
+     * @return Newly created <code>FetchLog</code> instance.
+     */
+    public static FetchLogPiped create(final ExecutorService executor,
+            final GlassFishServer server, final boolean skip) {
+        boolean isLocal = server.getDomainsFolder() != null
+                && server.getDomainName() != null;
+        FetchLogPiped fetchLog = isLocal
+                ? new FetchLogLocal(executor, server, skip)
+                : new FetchLogRemote(executor, server, skip);
+        fetchLog.start();
+        return fetchLog;
+    }
+
+    /**
+     * Constructs an instance of GlassFish server log fetcher depending
+     * on server being remote or local with external {@see ExecutorService}.
+     * <p/>
+     * Decision if server is local or remote depends on domains folder and
+     * domain name attributes stored in <code>GlassFishServer</code> object.
+     * Log file is passed whole as is without skipping to the end.
+     * <p/>
+     * @param executor Executor service used to start task.
+     * @param server   GlassFish server for fetching server log.
+     * @param skip     Skip to the end of the log file.
+     * @return Newly created <code>FetchLog</code> instance.
+     */
+    public static FetchLogPiped create(final ExecutorService executor,
+            final GlassFishServer server) {
+        boolean isLocal = server.getDomainsFolder() != null
+                && server.getDomainName() != null;
+        FetchLogPiped fetchLog = isLocal
+                ? new FetchLogLocal(executor, server, false)
+                : new FetchLogRemote(executor, server, false);
+        fetchLog.start();
+        return fetchLog;
+    }
+
 
     ////////////////////////////////////////////////////////////////////////////
     // Instance attributes                                                    //
@@ -81,7 +177,7 @@
     final PipedOutputStream out;
 
     /** Running task that reads log lines from remote server. */
-    final Future<TaskState> task;
+    Future<TaskState> task;
 
     /** Executor used to run read remote server log tasks. */
     private ExecutorService executor;
@@ -89,6 +185,9 @@
     /** Indicate whether log lines reading task should continue or exit. */
     volatile boolean taksExecute;
 
+    /** Listeners for state change events in GlassFish log fetcher. */
+    private final LinkedList<FetchLogEventListener> eventListeners;
+
     ////////////////////////////////////////////////////////////////////////////
     // Constructors                                                           //
     ////////////////////////////////////////////////////////////////////////////
@@ -106,9 +205,9 @@
      * @param server GlassFish server for fetching server log.
      * @param skip   Skip to the end of the log file.
      */
-    @SuppressWarnings("LeakingThisInConstructor")
     FetchLogPiped(final GlassFishServer server, boolean skip) {
         super(server, skip);
+        this.eventListeners = new LinkedList();
         try {
             out = new PipedOutputStream((PipedInputStream)this.in);
         } catch (IOException ioe) {
@@ -117,8 +216,7 @@
                     FetchLogException.OUTPUT_STREAM_EXCEPTION, ioe);
         }
         taksExecute = true;
-
-//        executor = Executors.newFixedThreadPool(1);
+        
         // Temporary fix to not show fetcher threads on NetBeans exit.
         executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
                 new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
@@ -131,7 +229,39 @@
             }
         });
 
-        task = executor.submit(this);
+    }
+
+    /**
+     * Constructs an instance of GlassFish remote server log fetcher with
+     * external {@see ExecutorService}.
+     * <p/>
+     * Super class constructor will call <code>initInputStream</code> method
+     * which initializes <code>InputStream</code> as
+     * <code>PipedInputStream</code> before this constructor code is being
+     * executed. Here we can simply connect already initialized
+     * <code>PipedInputStream</code> with newly created
+     * <code>PipedInputStream</code>.
+     * <p/>
+     * @param executor Executor service used to start task.
+     * @param server   GlassFish server for fetching server log.
+     * @param skip     Skip to the end of the log file.
+     */
+    @SuppressWarnings("LeakingThisInConstructor")
+    FetchLogPiped(final ExecutorService executor, final GlassFishServer server,
+            boolean skip) {
+        super(server, skip);
+        this.eventListeners = new LinkedList();
+        try {
+            out = new PipedOutputStream((PipedInputStream)this.in);
+        } catch (IOException ioe) {
+            super.close();
+            throw new FetchLogException(
+                    FetchLogException.OUTPUT_STREAM_EXCEPTION, ioe);
+        }
+        taksExecute = true;
+
+        // Temporary fix to not show fetcher threads on NetBeans exit.
+        this.executor = executor;
     }
 
     ////////////////////////////////////////////////////////////////////////////
@@ -161,6 +291,92 @@
     ////////////////////////////////////////////////////////////////////////////
 
     /**
+     * Add GlassFish log fetcher state change listener at the end
+     * of listeners list.
+     * <p/>
+     * @param listener Listener for state change events in GlassFish log fetcher
+     *                 to be added. Value shall not be <code>null</code>.
+     * @throws FetchLogException When <code>listener</code> parameter
+     *                           is <code>null</code>.
+     */
+    public final void addListener(final FetchLogEventListener listener)
+            throws FetchLogException {
+        if (listener == null) {
+            throw new FetchLogException(
+                    "Listener parameter shall not ne null.");
+        }
+        synchronized(eventListeners) {
+            eventListeners.addLast(listener);
+        }
+    }
+
+    /**
+     * Remove all occurrences of log fetcher state change listener
+     * from listeners list.
+     * <p/>
+     * @param listener Listener for state change events in GlassFish log fetcher
+     *                 to be removed. Value shall not be <code>null</code>.
+     * @return Value of <code>true</code> when at least one listener was removed
+     *         or <code>false</code> otherwise.
+     * @throws FetchLogException When <code>listener</code> parameter
+     *                           is <code>null</code>.
+     */
+    public final boolean removeListener(final FetchLogEventListener listener)
+            throws FetchLogException {
+        if (listener == null) {
+            throw new FetchLogException(
+                    "Listener parameter shall not ne null.");
+        }
+        boolean removed = false;
+        synchronized(eventListeners) {
+            boolean isElement = !eventListeners.isEmpty();
+            eventListeners.first();
+            while (isElement) {
+                if (listener.equals(eventListeners.getCurrent())) {
+                    isElement = eventListeners.isNext();
+                    eventListeners.removeAndNextOrPrevious();
+                    removed = true;
+                } else {
+                    isElement = eventListeners.next();
+                }
+            }
+        }
+        return removed;
+    }
+
+    /**
+     * Notify all GlassFish log fetcher state change listeners about state
+     * change event.
+     * <p/>
+     * @param state Current GlassFish log fetcher state.
+     * @return Current GlassFish log fetcher state.
+     */
+    final TaskState notifyListeners(final TaskState state) {
+        if (!eventListeners.isEmpty()) {
+            synchronized (eventListeners) {
+                boolean isElement = !eventListeners.isEmpty();
+                if (isElement) {
+                    FetchLogEvent event = new FetchLogEvent(state);
+                    eventListeners.first();
+                    while (isElement) {
+                        eventListeners.getCurrent().stateChanged(event);
+                        isElement = eventListeners.next();
+                    }
+                }
+            }
+        }
+        return state;
+    }
+
+    /**
+     * Start task.
+     */
+    private void start() {
+        task = executor.submit(this);
+        notifyListeners(TaskState.READY);
+    }
+
+    /**
      * Stop running task if it's still running.
      * <p/>
      * @return Task execution result.
diff -r 74660966b1bf -r 280234be9cb0 sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogRemote.java
--- a/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogRemote.java	Mon Apr 29 14:14:57 2013 +0200
+++ b/sdk/src/main/java/org/glassfish/tools/ide/server/FetchLogRemote.java	Tue May 14 16:27:01 2013 +0200
@@ -1,7 +1,7 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright 1997-2012 Oracle and/or its affiliates. All rights reserved.
+ * Copyright 1997-2013 Oracle and/or its affiliates. All rights reserved.
  *
  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  * Other names may be trademarks of their respective owners.
@@ -44,6 +44,7 @@
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.logging.Level;
 import org.glassfish.tools.ide.admin.CommandFetchLogData;
@@ -80,11 +81,30 @@
      * @param server GlassFish server for fetching server log.
      * @param skip   Skip to the end of the log file.
      */
-    @SuppressWarnings("LeakingThisInConstructor")
-    FetchLogRemote(GlassFishServer server, boolean skip) {
+    FetchLogRemote(final GlassFishServer server, final boolean skip) {
         super(server, skip);
     }
 
+    /**
+     * Constructs an instance of GlassFish remote server log fetcher with
+     * external {@see ExecutorService}.
+     * <p/>
+     * Super class constructor will call <code>initInputStream</code> method
+     * which initializes <code>InputStream</code> as
+     * <code>PipedInputStream</code> before this constructor code is being
+     * executed. Here we can simply connect already initialized
+     * <code>PipedInputStream</code> with newly created
+     * <code>PipedInputStream</code>.
+     * <p/>
+     * @param executor Executor service used to start task.
+     * @param server   GlassFish server for fetching server log.
+     * @param skip     Skip to the end of the log file.
+     */
+    FetchLogRemote(final ExecutorService executor, final GlassFishServer server,
+            final boolean skip) {
+        super(executor, server, skip);
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Runnable call() Method                                                  //
     ////////////////////////////////////////////////////////////////////////////
@@ -102,9 +122,9 @@
     @SuppressWarnings("SleepWhileInLoop")
     @Override
     public TaskState call() {
-        IdeContext ctx = new IdeContext();
+        notifyListeners(TaskState.RUNNING);
         Future<ResultLog> future = ServerAdmin.<ResultLog>exec(server,
-                new CommandFetchLogData(), ctx);
+                new CommandFetchLogData(), new IdeContext());
         try {
             ResultLog result = future.get();
             if (!skip && result.getState() == TaskState.COMPLETED) {
@@ -119,7 +139,7 @@
                 future = ServerAdmin.<ResultLog>exec(server,
                         new CommandFetchLogData(
                         result.getValue().getParamsAppendNext()),
-                        ctx);
+                        new IdeContext());
                 result = future.get();
                 if (result.getState() == TaskState.COMPLETED) {
                     for (String line : result.getValue().getLines()) {
@@ -139,26 +159,26 @@
             Logger.log(Level.INFO,
                     "Caught InterruptedException: " + ie.getMessage());
             Thread.currentThread().interrupt();
-            return TaskState.COMPLETED;
+            return notifyListeners(TaskState.COMPLETED);
         } catch (ExecutionException ee) {
             Logger.log(Level.INFO, "Caught ExecutionException: ", ee);
-            return TaskState.FAILED;
+            return notifyListeners(TaskState.FAILED);
         } catch (InterruptedIOException ie) {
             Logger.log(Level.INFO,
                     "Caught InterruptedIOException: " + ie.getMessage());
             Thread.currentThread().interrupt();
-            return TaskState.COMPLETED;
+            return notifyListeners(TaskState.COMPLETED);
         } catch (IOException ioe) {
             if (taksExecute) {
                 Logger.log(Level.INFO, "Caught IOException: ", ioe);
-                return TaskState.FAILED;
+                return notifyListeners(TaskState.FAILED);
             } else {
                 Logger.log(Level.INFO,
                         "Caught IOException: " + ioe.getMessage());
-                return TaskState.COMPLETED;
+                return notifyListeners(TaskState.COMPLETED);
             }
         }
-        return TaskState.COMPLETED;
+        return notifyListeners(TaskState.COMPLETED);
     }
 
 }
diff -r 74660966b1bf -r 280234be9cb0 sdk/src/test/java/org/glassfish/tools/ide/server/FetchLogTest.java
--- a/sdk/src/test/java/org/glassfish/tools/ide/server/FetchLogTest.java	Mon Apr 29 14:14:57 2013 +0200
+++ b/sdk/src/test/java/org/glassfish/tools/ide/server/FetchLogTest.java	Tue May 14 16:27:01 2013 +0200
@@ -71,7 +71,7 @@
                 || server.getDomainName() == null) {
             fail("Glassfish server cannot be used as local");
         }
-        FetchLog log = FetchLog.create(server, false);
+        FetchLog log = FetchLogPiped.create(server, false);
         StreamLinesList list = new StreamLinesList(log.getInputStream());
         try {
             Thread.sleep(2 * FetchLogRemote.LOG_REFRESH_DELAY);
@@ -130,7 +130,7 @@
                     fail("Cannot create empty log file:  "
                             + logFile.getAbsolutePath());
                 }
-                FetchLog log = FetchLog.create(server);
+                FetchLog log = FetchLogPiped.create(server);
                 FileWriter out = new FileWriter(logFile);
                 BufferedReader in = new BufferedReader(
                         new InputStreamReader(log.getInputStream()));
@@ -198,7 +198,7 @@
     public void testRemoteFetchLog() {
         GlassFishServer server = glassFishServer();
         ((GlassFishServerEntity)server).setDomainsFolder(null);
-        FetchLog log = FetchLog.create(server, false);
+        FetchLog log = FetchLogPiped.create(server, false);
         try {
             while (0 == log.getInputStream().available()) {
                 Thread.sleep(FetchLogRemote.LOG_REFRESH_DELAY);

--------------010805040901090205040105--