All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pkl.thirdparty.jline.utils.NonBlockingReaderImpl Maven / Gradle / Ivy

Go to download

Fat Jar containing pkl-cli, pkl-codegen-java, pkl-codegen-kotlin, pkl-config-java, pkl-core, pkl-doc, and their shaded third-party dependencies.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2002-2018, the original author(s).
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 *
 * https://opensource.org/licenses/BSD-3-Clause
 */
package org.pkl.thirdparty.jline.utils;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.Reader;

/**
 * This class wraps a regular reader and allows it to appear as if it
 * is non-blocking; that is, reads can be performed against it that timeout
 * if no data is seen for a period of time.  This effect is achieved by having
 * a separate thread perform all non-blocking read requests and then
 * waiting on the thread to complete.
 *
 * 

VERY IMPORTANT NOTES *

    *
  • This class is not thread safe. It expects at most one reader. *
  • The {@link #shutdown()} method must be called in order to shut down * the thread that handles blocking I/O. *
* @since 2.7 * @author Scott C. Gray <[email protected]> */ public class NonBlockingReaderImpl extends NonBlockingReader { public static final int READ_EXPIRED = -2; private Reader in; // The actual input stream private int ch = READ_EXPIRED; // Recently read character private String name; private boolean threadIsReading = false; private IOException exception = null; private long threadDelay = 60 * 1000; private Thread thread; /** * Creates a NonBlockingReader out of a normal blocking * reader. Note that this call also spawn a separate thread to perform the * blocking I/O on behalf of the thread that is using this class. The * {@link #shutdown()} method must be called in order to shut this thread down. * @param name The reader name * @param in The reader to wrap */ public NonBlockingReaderImpl(String name, Reader in) { this.in = in; this.name = name; } private synchronized void startReadingThreadIfNeeded() { if (thread == null) { thread = new Thread(this::run); thread.setName(name + " non blocking reader thread"); thread.setDaemon(true); thread.start(); } } /** * Shuts down the thread that is handling blocking I/O. Note that if the * thread is currently blocked waiting for I/O it will not actually * shut down until the I/O is received. */ public synchronized void shutdown() { if (thread != null) { notify(); } } @Override public void close() throws IOException { /* * The underlying input stream is closed first. This means that if the * I/O thread was blocked waiting on input, it will be woken for us. */ in.close(); shutdown(); } @Override public synchronized boolean ready() throws IOException { return ch >= 0 || in.ready(); } @Override public int readBuffered(char[] b, int off, int len, long timeout) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || off + len < b.length) { throw new IllegalArgumentException(); } else if (len == 0) { return 0; } else if (exception != null) { assert ch == READ_EXPIRED; IOException toBeThrown = exception; exception = null; throw toBeThrown; } else if (ch >= -1) { b[0] = (char) ch; ch = READ_EXPIRED; return 1; } else if (!threadIsReading && timeout <= 0) { return in.read(b, off, len); } else { // TODO: rework implementation to read as much as possible int c = read(timeout, false); if (c >= 0) { b[off] = (char) c; return 1; } else { return c; } } } /** * Attempts to read a character from the input stream for a specific * period of time. * @param timeout The amount of time to wait for the character * @return The character read, -1 if EOF is reached, or -2 if the * read timed out. */ protected synchronized int read(long timeout, boolean isPeek) throws IOException { /* * If the thread hit an IOException, we report it. */ if (exception != null) { assert ch == READ_EXPIRED; IOException toBeThrown = exception; if (!isPeek) exception = null; throw toBeThrown; } /* * If there was a pending character from the thread, then * we send it. If the timeout is 0L or the thread was shut down * then do a local read. */ if (ch >= -1) { assert exception == null; } else if (!isPeek && timeout <= 0L && !threadIsReading) { ch = in.read(); } else { /* * If the thread isn't reading already, then ask it to do so. */ if (!threadIsReading) { threadIsReading = true; startReadingThreadIfNeeded(); notifyAll(); } /* * So the thread is currently doing the reading for us. So * now we play the waiting game. */ Timeout t = new Timeout(timeout); while (!t.elapsed()) { try { if (Thread.interrupted()) { throw new InterruptedException(); } wait(t.timeout()); } catch (InterruptedException e) { exception = (IOException) new InterruptedIOException().initCause(e); } if (exception != null) { assert ch == READ_EXPIRED; IOException toBeThrown = exception; if (!isPeek) exception = null; throw toBeThrown; } if (ch >= -1) { assert exception == null; break; } } } /* * ch is the character that was just read. Either we set it because * a local read was performed or the read thread set it (or failed to * change it). We will return it's value, but if this was a peek * operation, then we leave it in place. */ int ret = ch; if (!isPeek) { ch = READ_EXPIRED; } return ret; } private void run() { Log.debug("NonBlockingReader start"); boolean needToRead; try { while (true) { /* * Synchronize to grab variables accessed by both this thread * and the accessing thread. */ synchronized (this) { needToRead = this.threadIsReading; try { /* * Nothing to do? Then wait. */ if (!needToRead) { wait(threadDelay); } } catch (InterruptedException e) { /* IGNORED */ } needToRead = this.threadIsReading; if (!needToRead) { return; } } /* * We're not shutting down, but we need to read. This cannot * happen while we are holding the lock (which we aren't now). */ int charRead = READ_EXPIRED; IOException failure = null; try { charRead = in.read(); // if (charRead < 0) { // continue; // } } catch (IOException e) { failure = e; // charRead = -1; } /* * Re-grab the lock to update the state. */ synchronized (this) { exception = failure; ch = charRead; threadIsReading = false; notify(); } } } catch (Throwable t) { Log.warn("Error in NonBlockingReader thread", t); } finally { Log.debug("NonBlockingReader shutdown"); synchronized (this) { thread = null; threadIsReading = false; } } } public synchronized void clear() throws IOException { while (ready()) { read(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy