PageRenderTime 32ms CodeModel.GetById 17ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/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 */
 24package hudson.remoting;
 25
 26import java.io.IOException;
 27import java.io.OutputStream;
 28import java.util.logging.Logger;
 29
 30import static java.util.logging.Level.FINER;
 31
 32/**
 33 * Keeps track of the number of bytes that the sender can send without overwhelming the receiver of the pipe.
 34 * <p/>
 35 * <p/>
 36 * {@link OutputStream} is a blocking operation in Java, so when we send byte[] to the remote to write to
 37 * {@link OutputStream}, it needs to be done in a separate thread (or else we'll fail to attend to the channel
 38 * in timely fashion.) This in turn means the byte[] being sent needs to go to a queue between a
 39 * channel reader thread and I/O processing thread, and thus in turn means we need some kind of throttling
 40 * mechanism, or else the queue can grow too much.
 41 * <p/>
 42 * <p/>
 43 * This implementation solves the problem by using TCP/IP like window size tracking. The sender allocates
 44 * a fixed length window size. Every time the sender sends something we reduce this value. When the receiver
 45 * writes data to {@link OutputStream}, it'll send back the "ack" command, which adds to this value, allowing
 46 * the sender to send more data.
 47 *
 48 * @author Kohsuke Kawaguchi
 49 */
 50abstract class PipeWindow {
 51
 52    protected Throwable dead;
 53
 54    abstract void increase(int delta);
 55
 56    abstract int peek();
 57
 58    /**
 59     * Blocks until some space becomes available.
 60     *
 61     * @return size.
 62     * @throws java.io.IOException  If we learned that there is an irrecoverable problem on the remote side that prevents us from writing.
 63     * @throws InterruptedException If a thread was interrupted while blocking.
 64     */
 65    abstract int get() throws InterruptedException, IOException;
 66
 67    abstract void decrease(int delta);
 68
 69    /**
 70     * Indicates that the remote end has died and all the further send attempt should fail.
 71     * @param cause {@link Throwable} cause.
 72     */
 73    void dead(Throwable cause) {
 74        this.dead = cause;
 75    }
 76
 77    /**
 78     * If we already know that the remote end had developed a problem, throw an exception.
 79     * Otherwise no-op.
 80     * @throws java.io.IOException exception.
 81     */
 82    protected void checkDeath() throws IOException {
 83        if (dead!=null)
 84            throw (IOException)new IOException("Pipe is already closed").initCause(dead);
 85    }
 86
 87    /**
 88     * Fake implementation used when the receiver side doesn't support throttling.
 89     */
 90    static class Fake extends PipeWindow {
 91        void increase(int delta) {
 92        }
 93
 94        int peek() {
 95            return Integer.MAX_VALUE;
 96        }
 97
 98        int get() throws InterruptedException, IOException {
 99            checkDeath();
100            return Integer.MAX_VALUE;
101        }
102
103        void decrease(int delta) {
104        }
105    }
106
107    static final class Key {
108        public final int oid;
109
110        Key(int oid) {
111            this.oid = oid;
112        }
113
114        @Override
115        public boolean equals(Object o) {
116            if (o == null || getClass() != o.getClass()) {
117                return false;
118            }
119
120            return oid == ((Key) o).oid;
121        }
122
123        @Override
124        public int hashCode() {
125            return oid;
126        }
127    }
128
129    static class Real extends PipeWindow {
130        private int available;
131        private long written;
132        private long acked;
133        private final int oid;
134        /**
135         * The only strong reference to the key, which in turn
136         * keeps this object accessible in {@link Channel#pipeWindows}.
137         */
138        private final Key key;
139
140        Real(Key key, int initialSize) {
141            this.key = key;
142            this.oid = key.oid;
143            this.available = initialSize;
144        }
145
146        public synchronized void increase(int delta) {
147            if (LOGGER.isLoggable(FINER)) {
148                LOGGER.finer(String.format("increase(%d,%d)->%d", oid, delta, delta + available));
149            }
150            available += delta;
151            acked += delta;
152            notifyAll();
153        }
154
155        public synchronized int peek() {
156            return available;
157        }
158
159        /**
160         * Blocks until some space becomes available.
161         *
162         * <p>
163         * If the window size is empty, induce some delay outside the synchronized block,
164         * to avoid fragmenting the window size. That is, if a bunch of small ACKs come in a sequence,
165         * bundle them up into a bigger size before making a call.
166         */
167        public int get() throws InterruptedException, IOException {
168            checkDeath();
169            synchronized (this) {
170                if (available>0)
171                    return available;
172
173                while (available<=0) {
174                    wait();
175                    checkDeath();
176                }
177            }
178
179            Thread.sleep(10);
180
181            synchronized (this) {
182                return available;
183            }
184        }
185
186        public synchronized void decrease(int delta) {
187            if (LOGGER.isLoggable(FINER)) {
188                LOGGER.finer(String.format("decrease(%d,%d)->%d", oid, delta, available - delta));
189            }
190            available -= delta;
191            written += delta;
192            /*
193            HUDSON-7745 says the following assertion fails, which AFAICT is only possible if multiple
194            threads write to OutputStream concurrently, but that doesn't happen in most of the situations, so
195            I'm puzzled. For the time being, cheating by just suppressing the assertion.
196
197            HUDSON-7581 appears to be related.
198            */
199//            if (available<0)
200//                throw new AssertionError();
201        }
202    }
203
204    private static final Logger LOGGER = Logger.getLogger(PipeWindow.class.getName());
205}