1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.nio.channels.spi;
19
20import java.io.IOException;
21import java.nio.channels.AsynchronousCloseException;
22import java.nio.channels.Channel;
23import java.nio.channels.ClosedByInterruptException;
24import java.nio.channels.InterruptibleChannel;
25
26/**
27 * {@code AbstractInterruptibleChannel} is the root class for interruptible
28 * channels.
29 * <p>
30 * The basic usage pattern for an interruptible channel is to invoke
31 * {@code begin()} before any I/O operation that potentially blocks
32 * indefinitely, then {@code end(boolean)} after completing the operation. The
33 * argument to the {@code end} method should indicate if the I/O operation has
34 * actually completed so that any change may be visible to the invoker.
35*/
36public abstract class AbstractInterruptibleChannel implements Channel, InterruptibleChannel {
37
38    private volatile boolean closed = false;
39
40    volatile boolean interrupted = false;
41
42    private final Runnable interruptAndCloseRunnable = new Runnable() {
43        @Override public void run() {
44            try {
45                interrupted = true;
46                AbstractInterruptibleChannel.this.close();
47            } catch (IOException ignored) {
48            }
49        }
50    };
51
52    protected AbstractInterruptibleChannel() {
53    }
54
55    @Override public synchronized final boolean isOpen() {
56        return !closed;
57    }
58
59    /**
60     * Closes an open channel. If the channel is already closed then this method
61     * has no effect, otherwise it closes the receiver via the
62     * {@code implCloseChannel} method.
63     * <p>
64     * If an attempt is made to perform an operation on a closed channel then a
65     * {@link java.nio.channels.ClosedChannelException} is thrown.
66     * <p>
67     * If multiple threads attempt to simultaneously close a channel, then only
68     * one thread will run the closure code and the others will be blocked until
69     * the first one completes.
70     *
71     * @throws IOException
72     *             if a problem occurs while closing this channel.
73     * @see java.nio.channels.Channel#close()
74     */
75    @Override public final void close() throws IOException {
76        if (!closed) {
77            synchronized (this) {
78                if (!closed) {
79                    closed = true;
80                    implCloseChannel();
81                }
82            }
83        }
84    }
85
86    /**
87     * Indicates the beginning of a code section that includes an I/O operation
88     * that is potentially blocking. After this operation, the application
89     * should invoke the corresponding {@code end(boolean)} method.
90     */
91    protected final void begin() {
92        Thread.currentThread().pushInterruptAction$(interruptAndCloseRunnable);
93    }
94
95    /**
96     * Indicates the end of a code section that has been started with
97     * {@code begin()} and that includes a potentially blocking I/O operation.
98     *
99     * @param success
100     *            pass {@code true} if the blocking operation has succeeded and
101     *            has had a noticeable effect; {@code false} otherwise.
102     * @throws AsynchronousCloseException
103     *             if this channel is closed by another thread while this method
104     *             is executing.
105     * @throws ClosedByInterruptException
106     *             if another thread interrupts the calling thread while this
107     *             method is executing.
108     */
109    protected final void end(boolean success) throws AsynchronousCloseException {
110        Thread.currentThread().popInterruptAction$(interruptAndCloseRunnable);
111        if (interrupted) {
112            interrupted = false;
113            throw new ClosedByInterruptException();
114        }
115        if (!success && closed) {
116            throw new AsynchronousCloseException();
117        }
118    }
119
120    /**
121     * Implements the channel closing behavior.
122     * <p>
123     * Closes the channel with a guarantee that the channel is not currently
124     * closed through another invocation of {@code close()} and that the method
125     * is thread-safe.
126     * <p>
127     * Any outstanding threads blocked on I/O operations on this channel must be
128     * released with either a normal return code, or by throwing an
129     * {@code AsynchronousCloseException}.
130     *
131     * @throws IOException
132     *             if a problem occurs while closing the channel.
133     */
134    protected abstract void implCloseChannel() throws IOException;
135}
136