Source:
public void rotationShouldNotDeadlockOnListener() throws Exception { String logContent = "Output file created"; final AtomicReference<Exception> listenerException = new AtomicReference<>( null ); CountDownLatch latch = new CountDownLatch( 1 ); RotationListener listener = new RotationListener() { @Override public void outputFileCreated( OutputStream out ) { try { Thread thread = new Thread( () -> { try { out.write( logContent.getBytes() ); out.flush(); } catch ( IOException e ) { listenerException.set( e ); } } ); thread.start(); thread.join(); } catch ( Exception e ) { listenerException.set( e ); } super.outputFileCreated( out ); } @Override public void rotationCompleted( OutputStream out ) { latch.countDown(); } }; ExecutorService executor = Executors.newSingleThreadExecutor(); DefaultFileSystemAbstraction defaultFileSystemAbstraction = new DefaultFileSystemAbstraction(); RotatingFileOutputStreamSupplier supplier = new RotatingFileOutputStreamSupplier( defaultFileSystemAbstraction, logFile, 0, 0, 10, executor, listener ); OutputStream outputStream = supplier.get(); LockingPrintWriter lockingPrintWriter = new LockingPrintWriter( outputStream ); lockingPrintWriter.withLock( () -> { supplier.rotate(); latch.await(); return Void.TYPE; } ); executor.shutdown(); boolean terminated = executor.awaitTermination( 20, TimeUnit.SECONDS ); if ( !terminated ) { throw new IllegalStateException( "Rotation execution failed to complete within reasonable time." ); } List<String> strings = Files.readAllLines( logFile.toPath() ); String actual = String.join( "", strings ); assertEquals( logContent, actual ); assertNull( listenerException.get() ); }

Target:
public void rotationShouldNotDeadlockOnListener() throws Exception { String logContent = "Output file created"; final AtomicReference<Exception> listenerException = new AtomicReference<>( null ); CountDownLatch latch = new CountDownLatch( 1 ); RotationListener listener = new RotationListener() { @Override public void outputFileCreated( OutputStream out ) { try { Thread thread = new Thread( () -> { try { out.write( logContent.getBytes() ); out.flush(); } catch ( IOException e ) { listenerException.set( e ); } } ); thread.start(); thread.join(); } catch ( Exception e ) { listenerException.set( e ); } super.outputFileCreated( out ); } @Override public void rotationCompleted( OutputStream out ) { latch.countDown(); } }; ExecutorService executor = Executors.newSingleThreadExecutor(); DefaultFileSystemAbstraction defaultFileSystemAbstraction = new DefaultFileSystemAbstraction(); RotatingFileOutputStreamSupplier supplier = new RotatingFileOutputStreamSupplier( defaultFileSystemAbstraction, logFile, 0, 0, 10, executor, listener ); OutputStream outputStream = supplier.get(); LockingPrintWriter lockingPrintWriter = new LockingPrintWriter( outputStream ); lockingPrintWriter.withLock( () -> { supplier.rotate(); latch.await(); return Void.TYPE; } ); executor.shutdown(); boolean terminated = executor.awaitTermination( TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); if ( !terminated ) { throw new IllegalStateException( "Rotation execution failed to complete within reasonable time." ); } List<String> strings = Files.readAllLines( logFile.toPath() ); String actual = String.join( "", strings ); assertEquals( logContent, actual ); assertNull( listenerException.get() ); }