bz-64733 Fix potential deadlock when writing out log message in junitlauncher task
diff --git a/WHATSNEW b/WHATSNEW
index 6cd482d..9d3f7c0 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -34,6 +34,10 @@
    single byte writes get the same treatment as array writes.
    Github Pull Request #145
 
+ * Fixes a potential deadlock in junitlauncher task when using legacy-xml
+   reporter.
+   Bugzilla Report 64733
+
 Other changes:
 --------------
 
diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LauncherSupport.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LauncherSupport.java
index 946ba51..d83f862 100644
--- a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LauncherSupport.java
+++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LauncherSupport.java
@@ -50,6 +50,7 @@
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.io.PrintStream;
+import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -137,8 +138,8 @@
                     final PrintStream originalSysOut = System.out;
                     final PrintStream originalSysErr = System.err;
                     try {
-                        firstListener.switchedSysOutHandle = trySwitchSysOutErr(testRequest, StreamType.SYS_OUT);
-                        firstListener.switchedSysErrHandle = trySwitchSysOutErr(testRequest, StreamType.SYS_ERR);
+                        firstListener.switchedSysOutHandle = trySwitchSysOutErr(testRequest, StreamType.SYS_OUT, originalSysErr);
+                        firstListener.switchedSysErrHandle = trySwitchSysOutErr(testRequest, StreamType.SYS_ERR, originalSysErr);
                         launcher.execute(request, testExecutionListeners.toArray(new TestExecutionListener[testExecutionListeners.size()]));
                     } finally {
                         // switch back sysout/syserr to the original
@@ -331,7 +332,8 @@
         }
     }
 
-    private Optional<SwitchedStreamHandle> trySwitchSysOutErr(final TestRequest testRequest, final StreamType streamType) {
+    private Optional<SwitchedStreamHandle> trySwitchSysOutErr(final TestRequest testRequest, final StreamType streamType,
+                                                              final PrintStream originalSysErr) {
         switch (streamType) {
             case SYS_OUT: {
                 if (!testRequest.interestedInSysOut()) {
@@ -365,22 +367,32 @@
             case SYS_OUT: {
                 System.setOut(new PrintStream(printStream));
                 streamer = new SysOutErrStreamReader(this, pipedInputStream,
-                        StreamType.SYS_OUT, testRequest.getSysOutInterests());
+                        StreamType.SYS_OUT, testRequest.getSysOutInterests(), originalSysErr);
                 final Thread sysOutStreamer = new Thread(streamer);
                 sysOutStreamer.setDaemon(true);
                 sysOutStreamer.setName("junitlauncher-sysout-stream-reader");
-                sysOutStreamer.setUncaughtExceptionHandler((t, e) -> this.log("Failed in sysout streaming", e, Project.MSG_INFO));
+                sysOutStreamer.setUncaughtExceptionHandler((t, e) -> {
+                    // skip the logging redirection infrastructure of junitlauncher task (which is what has
+                    // failed here) and instead directly write out the error to the original System.err
+                    originalSysErr.println("Failed in sysout streaming");
+                    e.printStackTrace(originalSysErr);
+                });
                 sysOutStreamer.start();
                 break;
             }
             case SYS_ERR: {
                 System.setErr(new PrintStream(printStream));
                 streamer = new SysOutErrStreamReader(this, pipedInputStream,
-                        StreamType.SYS_ERR, testRequest.getSysErrInterests());
+                        StreamType.SYS_ERR, testRequest.getSysErrInterests(), originalSysErr);
                 final Thread sysErrStreamer = new Thread(streamer);
                 sysErrStreamer.setDaemon(true);
                 sysErrStreamer.setName("junitlauncher-syserr-stream-reader");
-                sysErrStreamer.setUncaughtExceptionHandler((t, e) -> this.log("Failed in syserr streaming", e, Project.MSG_INFO));
+                sysErrStreamer.setUncaughtExceptionHandler((t, e) -> {
+                    // skip the logging redirection infrastructure of junitlauncher task (which is what has
+                    // failed here) and instead directly write out the error to the original System.err
+                    originalSysErr.println("Failed in syserr streaming");
+                    e.printStackTrace(originalSysErr);
+                });
                 sysErrStreamer.start();
                 break;
             }
@@ -477,20 +489,26 @@
         SYS_ERR
     }
 
+    // Implementation note: Logging from this class is prohibited since it can lead
+    // to deadlocks (see bz-64733 for details)
     private static final class SysOutErrStreamReader implements Runnable {
         private static final byte[] EMPTY = new byte[0];
 
         private final LauncherSupport launchManager;
+        private final PrintStream originalSysErr;
         private final InputStream sourceStream;
         private final StreamType streamType;
         private final Collection<TestResultFormatter> resultFormatters;
         private volatile SysOutErrContentDeliverer contentDeliverer;
 
-        SysOutErrStreamReader(final LauncherSupport launchManager, final InputStream source, final StreamType streamType, final Collection<TestResultFormatter> resultFormatters) {
+        SysOutErrStreamReader(final LauncherSupport launchManager, final InputStream source,
+                              final StreamType streamType, final Collection<TestResultFormatter> resultFormatters,
+                              final PrintStream originalSysErr) {
             this.launchManager = launchManager;
             this.sourceStream = source;
             this.streamType = streamType;
             this.resultFormatters = resultFormatters;
+            this.originalSysErr = originalSysErr;
         }
 
         @Override
@@ -509,8 +527,8 @@
                     streamContentDeliver.availableData.offer(copy);
                 }
             } catch (IOException e) {
-                this.launchManager.log("Failed while streaming " + (this.streamType == StreamType.SYS_OUT ? "sysout" : "syserr") + " data",
-                        e, Project.MSG_INFO);
+                // let the UncaughtExceptionHandler of this thread deal with this exception
+                throw new UncheckedIOException(e);
             } finally {
                 streamContentDeliver.stop = true;
                 // just "wakeup" the delivery thread, to take into account