public void parse(InputStream stream, ContentHandler handler, Metadata metadata,
                      ParseContext context) throws IOException, SAXException, TikaException {
        if (stream == null) {
            throw new NullPointerException("null stream");
        }

        Throwable t;

        boolean alive = false;
        ForkClient client = acquireClient();
        try {
            ContentHandler tee =
                    (handler instanceof AbstractRecursiveParserWrapperHandler) ? handler :
                            new TeeContentHandler(handler, new MetadataContentHandler(metadata));

            t = client.call("parse", stream, tee, metadata, context);
            alive = true;
        } catch (TikaException te) {
            // Problem occurred on our side
            alive = true;
            throw te;
        } catch (IOException e) {
            // Problem occurred on the other side
            throw new TikaException("Failed to communicate with a forked parser process." +
                    " The process has most likely crashed due to some error" +
                    " like running out of memory. A new process will be" +
                    " started for the next parsing request.", e);
        } finally {
            releaseClient(client, alive);
        }

        if (t instanceof IOException) {
            throw (IOException) t;
        } else if (t instanceof SAXException) {
            throw (SAXException) t;
        } else if (t instanceof TikaException) {
            throw (TikaException) t;
        } else if (t != null) {
            throw new TikaException("Unexpected error in forked server process", t);
        }
    }
@Test
    public void testForkParserDoesntPreventShutdown() throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(1);
        CountDownLatch cdl = new CountDownLatch(1);
        service.submit(() -> {
            try (ForkParser parser = new ForkParser(ForkParserTest.class.getClassLoader(),
                    new ForkTestParser.ForkTestParserWaiting())) {
                Metadata metadata = new Metadata();
                ContentHandler output = new BodyContentHandler();
                InputStream stream = new ByteArrayInputStream(new byte[0]);
                ParseContext context = new ParseContext();
                cdl.countDown();
                parser.parse(stream, output, metadata, context);
                // Don't care about output not planning to get this far
            } catch (IOException | SAXException | TikaException e) {
                throw new RuntimeException(e);
            }
        });
        // Wait to make sure submitted runnable is actually running
        boolean await = cdl.await(1, TimeUnit.SECONDS);
        if (!await) {
            // This should never happen but be thorough
            fail("Future never ran so cannot test cancellation");
        }
        // Parse is being called try and shutdown
        Instant requestShutdown = Instant.now();
        service.shutdownNow();
        service.awaitTermination(15, TimeUnit.SECONDS);
        long secondsSinceShutdown = ChronoUnit.SECONDS.between(requestShutdown, Instant.now());
        assertTrue(secondsSinceShutdown < 5, "Should have shutdown the service in less than 5 seconds");
    }