1 /* 2 * Copyright (c) 1995, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.lang; 27 28 import java.io.BufferedInputStream; 29 import java.io.BufferedOutputStream; 30 import java.io.ByteArrayInputStream; 31 import java.io.FileDescriptor; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.util.Arrays; 38 import java.util.EnumSet; 39 import java.util.Locale; 40 import java.util.Set; 41 import java.util.concurrent.Executors; 42 import java.util.concurrent.Executor; 43 import java.util.concurrent.ThreadFactory; 44 import java.util.concurrent.TimeUnit; 45 import java.security.AccessController; 46 import static java.security.AccessController.doPrivileged; 47 import java.security.PrivilegedAction; 48 import java.security.PrivilegedActionException; 49 import java.security.PrivilegedExceptionAction; 50 51 /** 52 * java.lang.Process subclass in the UNIX environment. 53 * 54 * @author Mario Wolczko and Ross Knippel. 55 * @author Konstantin Kladko (ported to Linux and Bsd) 56 * @author Martin Buchholz 57 * @author Volker Simonis (ported to AIX) 58 */ 59 final class UNIXProcess extends Process { 60 private static final sun.misc.JavaIOFileDescriptorAccess fdAccess 61 = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); 62 63 private final int pid; 64 private int exitcode; 65 private boolean hasExited; 66 67 private /* final */ OutputStream stdin; 68 private /* final */ InputStream stdout; 69 private /* final */ InputStream stderr; 70 71 // only used on Solaris 72 private /* final */ DeferredCloseInputStream stdout_inner_stream; 73 74 private static enum LaunchMechanism { 75 // order IS important! 76 FORK, 77 POSIX_SPAWN, 78 VFORK 79 } 80 81 private static enum Platform { 82 83 LINUX(LaunchMechanism.VFORK, LaunchMechanism.FORK), 84 85 BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK), 86 87 SOLARIS(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK), 88 89 AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK); 90 91 final LaunchMechanism defaultLaunchMechanism; 92 final Set<LaunchMechanism> validLaunchMechanisms; 93 94 Platform(LaunchMechanism ... launchMechanisms) { 95 this.defaultLaunchMechanism = launchMechanisms[0]; 96 this.validLaunchMechanisms = 97 EnumSet.copyOf(Arrays.asList(launchMechanisms)); 98 } 99 100 private String helperPath(String javahome, String osArch) { 101 switch (this) { 102 case SOLARIS: 103 if (osArch.equals("x86")) { osArch = "i386"; } 104 else if (osArch.equals("x86_64")) { osArch = "amd64"; } 105 // fall through... 106 case LINUX: 107 case AIX: 108 return javahome + "/lib/" + osArch + "/jspawnhelper"; 109 110 case BSD: 111 return javahome + "/lib/jspawnhelper"; 112 113 default: 114 throw new AssertionError("Unsupported platform: " + this); 115 } 116 } 117 118 String helperPath() { 119 return AccessController.doPrivileged( 120 (PrivilegedAction<String>) () -> 121 helperPath(System.getProperty("java.home"), 122 System.getProperty("os.arch")) 123 ); 124 } 125 126 LaunchMechanism launchMechanism() { 127 return AccessController.doPrivileged( 128 (PrivilegedAction<LaunchMechanism>) () -> { 129 String s = System.getProperty( 130 "jdk.lang.Process.launchMechanism"); 131 LaunchMechanism lm; 132 if (s == null) { 133 lm = defaultLaunchMechanism; 134 s = lm.name().toLowerCase(Locale.ENGLISH); 135 } else { 136 try { 137 lm = LaunchMechanism.valueOf( 138 s.toUpperCase(Locale.ENGLISH)); 139 } catch (IllegalArgumentException e) { 140 lm = null; 141 } 142 } 143 if (lm == null || !validLaunchMechanisms.contains(lm)) { 144 throw new Error( 145 s + " is not a supported " + 146 "process launch mechanism on this platform." 147 ); 148 } 149 return lm; 150 } 151 ); 152 } 153 154 static Platform get() { 155 String osName = AccessController.doPrivileged( 156 (PrivilegedAction<String>) () -> System.getProperty("os.name") 157 ); 158 159 if (osName.equals("Linux")) { return LINUX; } 160 if (osName.contains("OS X")) { return BSD; } 161 if (osName.equals("SunOS")) { return SOLARIS; } 162 if (osName.equals("AIX")) { return AIX; } 163 164 throw new Error(osName + " is not a supported OS platform."); 165 } 166 } 167 168 private static final Platform platform = Platform.get(); 169 private static final LaunchMechanism launchMechanism = platform.launchMechanism(); 170 private static final byte[] helperpath = toCString(platform.helperPath()); 171 172 private static byte[] toCString(String s) { 173 if (s == null) 174 return null; 175 byte[] bytes = s.getBytes(); 176 byte[] result = new byte[bytes.length + 1]; 177 System.arraycopy(bytes, 0, 178 result, 0, 179 bytes.length); 180 result[result.length-1] = (byte)0; 181 return result; 182 } 183 184 /* this is for the reaping thread */ 185 private native int waitForProcessExit(int pid); 186 187 /** 188 * Create a process. Depending on the mode flag, this is done by 189 * one of the following mechanisms. 190 * - fork(2) and exec(2) 191 * - clone(2) and exec(2) 192 * - vfork(2) and exec(2) 193 * 194 * @param fds an array of three file descriptors. 195 * Indexes 0, 1, and 2 correspond to standard input, 196 * standard output and standard error, respectively. On 197 * input, a value of -1 means to create a pipe to connect 198 * child and parent processes. On output, a value which 199 * is not -1 is the parent pipe fd corresponding to the 200 * pipe which has been created. An element of this array 201 * is -1 on input if and only if it is <em>not</em> -1 on 202 * output. 203 * @return the pid of the subprocess 204 */ 205 private native int forkAndExec(int mode, byte[] helperpath, 206 byte[] prog, 207 byte[] argBlock, int argc, 208 byte[] envBlock, int envc, 209 byte[] dir, 210 int[] fds, 211 boolean redirectErrorStream) 212 throws IOException; 213 214 /** 215 * The thread pool of "process reaper" daemon threads. 216 */ 217 private static final Executor processReaperExecutor = 218 doPrivileged((PrivilegedAction<Executor>) () -> { 219 220 ThreadGroup tg = Thread.currentThread().getThreadGroup(); 221 while (tg.getParent() != null) tg = tg.getParent(); 222 ThreadGroup systemThreadGroup = tg; 223 224 ThreadFactory threadFactory = grimReaper -> { 225 // Our thread stack requirement is quite modest. 226 Thread t = new Thread(systemThreadGroup, grimReaper, 227 "process reaper", 32768); 228 t.setDaemon(true); 229 // A small attempt (probably futile) to avoid priority inversion 230 t.setPriority(Thread.MAX_PRIORITY); 231 return t; 232 }; 233 234 return Executors.newCachedThreadPool(threadFactory); 235 }); 236 237 UNIXProcess(final byte[] prog, 238 final byte[] argBlock, final int argc, 239 final byte[] envBlock, final int envc, 240 final byte[] dir, 241 final int[] fds, 242 final boolean redirectErrorStream) 243 throws IOException { 244 245 pid = forkAndExec(launchMechanism.ordinal() + 1, 246 helperpath, 247 prog, 248 argBlock, argc, 249 envBlock, envc, 250 dir, 251 fds, 252 redirectErrorStream); 253 254 try { 255 doPrivileged((PrivilegedExceptionAction<Void>) () -> { 256 initStreams(fds); 257 return null; 258 }); 259 } catch (PrivilegedActionException ex) { 260 throw (IOException) ex.getException(); 261 } 262 } 263 264 static FileDescriptor newFileDescriptor(int fd) { 265 FileDescriptor fileDescriptor = new FileDescriptor(); 266 fdAccess.set(fileDescriptor, fd); 267 return fileDescriptor; 268 } 269 270 void initStreams(int[] fds) throws IOException { 271 switch (platform) { 272 case LINUX: 273 case BSD: 274 stdin = (fds[0] == -1) ? 275 ProcessBuilder.NullOutputStream.INSTANCE : 276 new ProcessPipeOutputStream(fds[0]); 277 278 stdout = (fds[1] == -1) ? 279 ProcessBuilder.NullInputStream.INSTANCE : 280 new ProcessPipeInputStream(fds[1]); 281 282 stderr = (fds[2] == -1) ? 283 ProcessBuilder.NullInputStream.INSTANCE : 284 new ProcessPipeInputStream(fds[2]); 285 286 processReaperExecutor.execute(() -> { 287 int exitcode = waitForProcessExit(pid); 288 289 synchronized (this) { 290 this.exitcode = exitcode; 291 this.hasExited = true; 292 this.notifyAll(); 293 } 294 295 if (stdout instanceof ProcessPipeInputStream) 296 ((ProcessPipeInputStream) stdout).processExited(); 297 298 if (stderr instanceof ProcessPipeInputStream) 299 ((ProcessPipeInputStream) stderr).processExited(); 300 301 if (stdin instanceof ProcessPipeOutputStream) 302 ((ProcessPipeOutputStream) stdin).processExited(); 303 }); 304 break; 305 306 case SOLARIS: 307 stdin = (fds[0] == -1) ? 308 ProcessBuilder.NullOutputStream.INSTANCE : 309 new BufferedOutputStream( 310 new FileOutputStream(newFileDescriptor(fds[0]))); 311 312 stdout = (fds[1] == -1) ? 313 ProcessBuilder.NullInputStream.INSTANCE : 314 new BufferedInputStream( 315 stdout_inner_stream = 316 new DeferredCloseInputStream( 317 newFileDescriptor(fds[1]))); 318 319 stderr = (fds[2] == -1) ? 320 ProcessBuilder.NullInputStream.INSTANCE : 321 new DeferredCloseInputStream(newFileDescriptor(fds[2])); 322 323 /* 324 * For each subprocess forked a corresponding reaper task 325 * is submitted. That task is the only thread which waits 326 * for the subprocess to terminate and it doesn't hold any 327 * locks while doing so. This design allows waitFor() and 328 * exitStatus() to be safely executed in parallel (and they 329 * need no native code). 330 */ 331 processReaperExecutor.execute(() -> { 332 int exitcode = waitForProcessExit(pid); 333 334 synchronized (this) { 335 this.exitcode = exitcode; 336 this.hasExited = true; 337 this.notifyAll(); 338 } 339 }); 340 break; 341 342 case AIX: 343 stdin = (fds[0] == -1) ? 344 ProcessBuilder.NullOutputStream.INSTANCE : 345 new ProcessPipeOutputStream(fds[0]); 346 347 stdout = (fds[1] == -1) ? 348 ProcessBuilder.NullInputStream.INSTANCE : 349 new DeferredCloseProcessPipeInputStream(fds[1]); 350 351 stderr = (fds[2] == -1) ? 352 ProcessBuilder.NullInputStream.INSTANCE : 353 new DeferredCloseProcessPipeInputStream(fds[2]); 354 355 processReaperExecutor.execute(() -> { 356 int exitcode = waitForProcessExit(pid); 357 358 synchronized (this) { 359 this.exitcode = exitcode; 360 this.hasExited = true; 361 this.notifyAll(); 362 } 363 364 if (stdout instanceof DeferredCloseProcessPipeInputStream) 365 ((DeferredCloseProcessPipeInputStream) stdout).processExited(); 366 367 if (stderr instanceof DeferredCloseProcessPipeInputStream) 368 ((DeferredCloseProcessPipeInputStream) stderr).processExited(); 369 370 if (stdin instanceof ProcessPipeOutputStream) 371 ((ProcessPipeOutputStream) stdin).processExited(); 372 }); 373 break; 374 375 default: throw new AssertionError("Unsupported platform: " + platform); 376 } 377 } 378 379 public OutputStream getOutputStream() { 380 return stdin; 381 } 382 383 public InputStream getInputStream() { 384 return stdout; 385 } 386 387 public InputStream getErrorStream() { 388 return stderr; 389 } 390 391 public synchronized int waitFor() throws InterruptedException { 392 while (!hasExited) { 393 wait(); 394 } 395 return exitcode; 396 } 397 398 @Override 399 public synchronized boolean waitFor(long timeout, TimeUnit unit) 400 throws InterruptedException 401 { 402 if (hasExited) return true; 403 if (timeout <= 0) return false; 404 405 long timeoutAsNanos = unit.toNanos(timeout); 406 long startTime = System.nanoTime(); 407 long rem = timeoutAsNanos; 408 409 while (!hasExited && (rem > 0)) { 410 wait(Math.max(TimeUnit.NANOSECONDS.toMillis(rem), 1)); 411 rem = timeoutAsNanos - (System.nanoTime() - startTime); 412 } 413 return hasExited; 414 } 415 416 public synchronized int exitValue() { 417 if (!hasExited) { 418 throw new IllegalThreadStateException("process hasn't exited"); 419 } 420 return exitcode; 421 } 422 423 private static native void destroyProcess(int pid, boolean force); 424 425 private void destroy(boolean force) { 426 switch (platform) { 427 case LINUX: 428 case BSD: 429 case AIX: 430 // There is a risk that pid will be recycled, causing us to 431 // kill the wrong process! So we only terminate processes 432 // that appear to still be running. Even with this check, 433 // there is an unavoidable race condition here, but the window 434 // is very small, and OSes try hard to not recycle pids too 435 // soon, so this is quite safe. 436 synchronized (this) { 437 if (!hasExited) 438 destroyProcess(pid, force); 439 } 440 try { stdin.close(); } catch (IOException ignored) {} 441 try { stdout.close(); } catch (IOException ignored) {} 442 try { stderr.close(); } catch (IOException ignored) {} 443 break; 444 445 case SOLARIS: 446 // There is a risk that pid will be recycled, causing us to 447 // kill the wrong process! So we only terminate processes 448 // that appear to still be running. Even with this check, 449 // there is an unavoidable race condition here, but the window 450 // is very small, and OSes try hard to not recycle pids too 451 // soon, so this is quite safe. 452 synchronized (this) { 453 if (!hasExited) 454 destroyProcess(pid, force); 455 try { 456 stdin.close(); 457 if (stdout_inner_stream != null) 458 stdout_inner_stream.closeDeferred(stdout); 459 if (stderr instanceof DeferredCloseInputStream) 460 ((DeferredCloseInputStream) stderr) 461 .closeDeferred(stderr); 462 } catch (IOException e) { 463 // ignore 464 } 465 } 466 break; 467 468 default: throw new AssertionError("Unsupported platform: " + platform); 469 } 470 } 471 472 public void destroy() { 473 destroy(false); 474 } 475 476 @Override 477 public Process destroyForcibly() { 478 destroy(true); 479 return this; 480 } 481 482 @Override 483 public synchronized boolean isAlive() { 484 return !hasExited; 485 } 486 487 private static native void init(); 488 489 static { 490 init(); 491 } 492 493 /** 494 * A buffered input stream for a subprocess pipe file descriptor 495 * that allows the underlying file descriptor to be reclaimed when 496 * the process exits, via the processExited hook. 497 * 498 * This is tricky because we do not want the user-level InputStream to be 499 * closed until the user invokes close(), and we need to continue to be 500 * able to read any buffered data lingering in the OS pipe buffer. 501 */ 502 private static class ProcessPipeInputStream extends BufferedInputStream { 503 private final Object closeLock = new Object(); 504 505 ProcessPipeInputStream(int fd) { 506 super(new FileInputStream(newFileDescriptor(fd))); 507 } 508 private static byte[] drainInputStream(InputStream in) 509 throws IOException { 510 int n = 0; 511 int j; 512 byte[] a = null; 513 while ((j = in.available()) > 0) { 514 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j); 515 n += in.read(a, n, j); 516 } 517 return (a == null || n == a.length) ? a : Arrays.copyOf(a, n); 518 } 519 520 /** Called by the process reaper thread when the process exits. */ 521 synchronized void processExited() { 522 synchronized (closeLock) { 523 try { 524 InputStream in = this.in; 525 // this stream is closed if and only if: in == null 526 if (in != null) { 527 byte[] stragglers = drainInputStream(in); 528 in.close(); 529 this.in = (stragglers == null) ? 530 ProcessBuilder.NullInputStream.INSTANCE : 531 new ByteArrayInputStream(stragglers); 532 } 533 } catch (IOException ignored) {} 534 } 535 } 536 537 @Override 538 public void close() throws IOException { 539 // BufferedInputStream#close() is not synchronized unlike most other 540 // methods. Synchronizing helps avoid race with processExited(). 541 synchronized (closeLock) { 542 super.close(); 543 } 544 } 545 } 546 547 /** 548 * A buffered output stream for a subprocess pipe file descriptor 549 * that allows the underlying file descriptor to be reclaimed when 550 * the process exits, via the processExited hook. 551 */ 552 private static class ProcessPipeOutputStream extends BufferedOutputStream { 553 ProcessPipeOutputStream(int fd) { 554 super(new FileOutputStream(newFileDescriptor(fd))); 555 } 556 557 /** Called by the process reaper thread when the process exits. */ 558 synchronized void processExited() { 559 OutputStream out = this.out; 560 if (out != null) { 561 try { 562 out.close(); 563 } catch (IOException ignored) { 564 // We know of no reason to get an IOException, but if 565 // we do, there's nothing else to do but carry on. 566 } 567 this.out = ProcessBuilder.NullOutputStream.INSTANCE; 568 } 569 } 570 } 571 572 // A FileInputStream that supports the deferment of the actual close 573 // operation until the last pending I/O operation on the stream has 574 // finished. This is required on Solaris because we must close the stdin 575 // and stdout streams in the destroy method in order to reclaim the 576 // underlying file descriptors. Doing so, however, causes any thread 577 // currently blocked in a read on one of those streams to receive an 578 // IOException("Bad file number"), which is incompatible with historical 579 // behavior. By deferring the close we allow any pending reads to see -1 580 // (EOF) as they did before. 581 // 582 private static class DeferredCloseInputStream extends FileInputStream 583 { 584 DeferredCloseInputStream(FileDescriptor fd) { 585 super(fd); 586 } 587 588 private Object lock = new Object(); // For the following fields 589 private boolean closePending = false; 590 private int useCount = 0; 591 private InputStream streamToClose; 592 593 private void raise() { 594 synchronized (lock) { 595 useCount++; 596 } 597 } 598 599 private void lower() throws IOException { 600 synchronized (lock) { 601 useCount--; 602 if (useCount == 0 && closePending) { 603 streamToClose.close(); 604 } 605 } 606 } 607 608 // stc is the actual stream to be closed; it might be this object, or 609 // it might be an upstream object for which this object is downstream. 610 // 611 private void closeDeferred(InputStream stc) throws IOException { 612 synchronized (lock) { 613 if (useCount == 0) { 614 stc.close(); 615 } else { 616 closePending = true; 617 streamToClose = stc; 618 } 619 } 620 } 621 622 public void close() throws IOException { 623 synchronized (lock) { 624 useCount = 0; 625 closePending = false; 626 } 627 super.close(); 628 } 629 630 public int read() throws IOException { 631 raise(); 632 try { 633 return super.read(); 634 } finally { 635 lower(); 636 } 637 } 638 639 public int read(byte[] b) throws IOException { 640 raise(); 641 try { 642 return super.read(b); 643 } finally { 644 lower(); 645 } 646 } 647 648 public int read(byte[] b, int off, int len) throws IOException { 649 raise(); 650 try { 651 return super.read(b, off, len); 652 } finally { 653 lower(); 654 } 655 } 656 657 public long skip(long n) throws IOException { 658 raise(); 659 try { 660 return super.skip(n); 661 } finally { 662 lower(); 663 } 664 } 665 666 public int available() throws IOException { 667 raise(); 668 try { 669 return super.available(); 670 } finally { 671 lower(); 672 } 673 } 674 } 675 676 /** 677 * A buffered input stream for a subprocess pipe file descriptor 678 * that allows the underlying file descriptor to be reclaimed when 679 * the process exits, via the processExited hook. 680 * 681 * This is tricky because we do not want the user-level InputStream to be 682 * closed until the user invokes close(), and we need to continue to be 683 * able to read any buffered data lingering in the OS pipe buffer. 684 * 685 * On AIX this is especially tricky, because the 'close()' system call 686 * will block if another thread is at the same time blocked in a file 687 * operation (e.g. 'read()') on the same file descriptor. We therefore 688 * combine 'ProcessPipeInputStream' approach used on Linux and Bsd 689 * with the DeferredCloseInputStream approach used on Solaris. This means 690 * that every potentially blocking operation on the file descriptor 691 * increments a counter before it is executed and decrements it once it 692 * finishes. The 'close()' operation will only be executed if there are 693 * no pending operations. Otherwise it is deferred after the last pending 694 * operation has finished. 695 * 696 */ 697 private static class DeferredCloseProcessPipeInputStream 698 extends BufferedInputStream { 699 700 private final Object closeLock = new Object(); 701 private int useCount = 0; 702 private boolean closePending = false; 703 704 DeferredCloseProcessPipeInputStream(int fd) { 705 super(new FileInputStream(newFileDescriptor(fd))); 706 } 707 708 private InputStream drainInputStream(InputStream in) 709 throws IOException { 710 int n = 0; 711 int j; 712 byte[] a = null; 713 synchronized (closeLock) { 714 if (buf == null) // asynchronous close()? 715 return null; // discard 716 j = in.available(); 717 } 718 while (j > 0) { 719 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j); 720 synchronized (closeLock) { 721 if (buf == null) // asynchronous close()? 722 return null; // discard 723 n += in.read(a, n, j); 724 j = in.available(); 725 } 726 } 727 return (a == null) ? 728 ProcessBuilder.NullInputStream.INSTANCE : 729 new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n)); 730 } 731 732 /** Called by the process reaper thread when the process exits. */ 733 synchronized void processExited() { 734 try { 735 InputStream in = this.in; 736 if (in != null) { 737 InputStream stragglers = drainInputStream(in); 738 in.close(); 739 this.in = stragglers; 740 } 741 } catch (IOException ignored) { } 742 } 743 744 private void raise() { 745 synchronized (closeLock) { 746 useCount++; 747 } 748 } 749 750 private void lower() throws IOException { 751 synchronized (closeLock) { 752 useCount--; 753 if (useCount == 0 && closePending) { 754 closePending = false; 755 super.close(); 756 } 757 } 758 } 759 760 @Override 761 public int read() throws IOException { 762 raise(); 763 try { 764 return super.read(); 765 } finally { 766 lower(); 767 } 768 } 769 770 @Override 771 public int read(byte[] b) throws IOException { 772 raise(); 773 try { 774 return super.read(b); 775 } finally { 776 lower(); 777 } 778 } 779 780 @Override 781 public int read(byte[] b, int off, int len) throws IOException { 782 raise(); 783 try { 784 return super.read(b, off, len); 785 } finally { 786 lower(); 787 } 788 } 789 790 @Override 791 public long skip(long n) throws IOException { 792 raise(); 793 try { 794 return super.skip(n); 795 } finally { 796 lower(); 797 } 798 } 799 800 @Override 801 public int available() throws IOException { 802 raise(); 803 try { 804 return super.available(); 805 } finally { 806 lower(); 807 } 808 } 809 810 @Override 811 public void close() throws IOException { 812 // BufferedInputStream#close() is not synchronized unlike most other 813 // methods. Synchronizing helps avoid racing with drainInputStream(). 814 synchronized (closeLock) { 815 if (useCount == 0) { 816 super.close(); 817 } 818 else { 819 closePending = true; 820 } 821 } 822 } 823 } 824 }