/hudson-remoting/src/main/java/hudson/remoting/PipeWindow.java

http://github.com/hudson/hudson · Java · 205 lines · 95 code · 29 blank · 81 comment · 10 complexity · 4ad5e75b485bd5930ba7315995d20acf MD5 · raw file

  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2010, InfraDNA, Inc.
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. package hudson.remoting;
  25. import java.io.IOException;
  26. import java.io.OutputStream;
  27. import java.util.logging.Logger;
  28. import static java.util.logging.Level.FINER;
  29. /**
  30. * Keeps track of the number of bytes that the sender can send without overwhelming the receiver of the pipe.
  31. * <p/>
  32. * <p/>
  33. * {@link OutputStream} is a blocking operation in Java, so when we send byte[] to the remote to write to
  34. * {@link OutputStream}, it needs to be done in a separate thread (or else we'll fail to attend to the channel
  35. * in timely fashion.) This in turn means the byte[] being sent needs to go to a queue between a
  36. * channel reader thread and I/O processing thread, and thus in turn means we need some kind of throttling
  37. * mechanism, or else the queue can grow too much.
  38. * <p/>
  39. * <p/>
  40. * This implementation solves the problem by using TCP/IP like window size tracking. The sender allocates
  41. * a fixed length window size. Every time the sender sends something we reduce this value. When the receiver
  42. * writes data to {@link OutputStream}, it'll send back the "ack" command, which adds to this value, allowing
  43. * the sender to send more data.
  44. *
  45. * @author Kohsuke Kawaguchi
  46. */
  47. abstract class PipeWindow {
  48. protected Throwable dead;
  49. abstract void increase(int delta);
  50. abstract int peek();
  51. /**
  52. * Blocks until some space becomes available.
  53. *
  54. * @return size.
  55. * @throws java.io.IOException If we learned that there is an irrecoverable problem on the remote side that prevents us from writing.
  56. * @throws InterruptedException If a thread was interrupted while blocking.
  57. */
  58. abstract int get() throws InterruptedException, IOException;
  59. abstract void decrease(int delta);
  60. /**
  61. * Indicates that the remote end has died and all the further send attempt should fail.
  62. * @param cause {@link Throwable} cause.
  63. */
  64. void dead(Throwable cause) {
  65. this.dead = cause;
  66. }
  67. /**
  68. * If we already know that the remote end had developed a problem, throw an exception.
  69. * Otherwise no-op.
  70. * @throws java.io.IOException exception.
  71. */
  72. protected void checkDeath() throws IOException {
  73. if (dead!=null)
  74. throw (IOException)new IOException("Pipe is already closed").initCause(dead);
  75. }
  76. /**
  77. * Fake implementation used when the receiver side doesn't support throttling.
  78. */
  79. static class Fake extends PipeWindow {
  80. void increase(int delta) {
  81. }
  82. int peek() {
  83. return Integer.MAX_VALUE;
  84. }
  85. int get() throws InterruptedException, IOException {
  86. checkDeath();
  87. return Integer.MAX_VALUE;
  88. }
  89. void decrease(int delta) {
  90. }
  91. }
  92. static final class Key {
  93. public final int oid;
  94. Key(int oid) {
  95. this.oid = oid;
  96. }
  97. @Override
  98. public boolean equals(Object o) {
  99. if (o == null || getClass() != o.getClass()) {
  100. return false;
  101. }
  102. return oid == ((Key) o).oid;
  103. }
  104. @Override
  105. public int hashCode() {
  106. return oid;
  107. }
  108. }
  109. static class Real extends PipeWindow {
  110. private int available;
  111. private long written;
  112. private long acked;
  113. private final int oid;
  114. /**
  115. * The only strong reference to the key, which in turn
  116. * keeps this object accessible in {@link Channel#pipeWindows}.
  117. */
  118. private final Key key;
  119. Real(Key key, int initialSize) {
  120. this.key = key;
  121. this.oid = key.oid;
  122. this.available = initialSize;
  123. }
  124. public synchronized void increase(int delta) {
  125. if (LOGGER.isLoggable(FINER)) {
  126. LOGGER.finer(String.format("increase(%d,%d)->%d", oid, delta, delta + available));
  127. }
  128. available += delta;
  129. acked += delta;
  130. notifyAll();
  131. }
  132. public synchronized int peek() {
  133. return available;
  134. }
  135. /**
  136. * Blocks until some space becomes available.
  137. *
  138. * <p>
  139. * If the window size is empty, induce some delay outside the synchronized block,
  140. * to avoid fragmenting the window size. That is, if a bunch of small ACKs come in a sequence,
  141. * bundle them up into a bigger size before making a call.
  142. */
  143. public int get() throws InterruptedException, IOException {
  144. checkDeath();
  145. synchronized (this) {
  146. if (available>0)
  147. return available;
  148. while (available<=0) {
  149. wait();
  150. checkDeath();
  151. }
  152. }
  153. Thread.sleep(10);
  154. synchronized (this) {
  155. return available;
  156. }
  157. }
  158. public synchronized void decrease(int delta) {
  159. if (LOGGER.isLoggable(FINER)) {
  160. LOGGER.finer(String.format("decrease(%d,%d)->%d", oid, delta, available - delta));
  161. }
  162. available -= delta;
  163. written += delta;
  164. /*
  165. HUDSON-7745 says the following assertion fails, which AFAICT is only possible if multiple
  166. threads write to OutputStream concurrently, but that doesn't happen in most of the situations, so
  167. I'm puzzled. For the time being, cheating by just suppressing the assertion.
  168. HUDSON-7581 appears to be related.
  169. */
  170. // if (available<0)
  171. // throw new AssertionError();
  172. }
  173. }
  174. private static final Logger LOGGER = Logger.getLogger(PipeWindow.class.getName());
  175. }