Fixed deadlock between AsyncSshHandlerReader and AsyncSshHandler
[netconf.git] / netconf / netconf-netty-util / src / main / java / org / opendaylight / netconf / nettyutil / handler / ssh / client / AsyncSshHandlerReader.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.netconf.nettyutil.handler.ssh.client;
10
11 import io.netty.buffer.ByteBuf;
12 import io.netty.buffer.Unpooled;
13 import org.apache.sshd.common.future.SshFutureListener;
14 import org.apache.sshd.common.io.IoInputStream;
15 import org.apache.sshd.common.io.IoReadFuture;
16 import org.apache.sshd.common.util.buffer.Buffer;
17 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20
21 /**
22  * Listener on async input stream from SSH session.
23  * This listeners schedules reads in a loop until the session is closed or read fails.
24  */
25 public final class AsyncSshHandlerReader implements SshFutureListener<IoReadFuture>, AutoCloseable {
26
27     private static final Logger LOG = LoggerFactory.getLogger(AsyncSshHandlerReader.class);
28
29     private static final int BUFFER_SIZE = 2048;
30
31     private final AutoCloseable connectionClosedCallback;
32     private final ReadMsgHandler readHandler;
33
34     private final String channelId;
35     private IoInputStream asyncOut;
36     private Buffer buf;
37     private IoReadFuture currentReadFuture;
38
39     public AsyncSshHandlerReader(final AutoCloseable connectionClosedCallback, final ReadMsgHandler readHandler,
40                                  final String channelId, final IoInputStream asyncOut) {
41         this.connectionClosedCallback = connectionClosedCallback;
42         this.readHandler = readHandler;
43         this.channelId = channelId;
44         this.asyncOut = asyncOut;
45         buf = new ByteArrayBuffer(BUFFER_SIZE);
46         asyncOut.read(buf).addListener(this);
47     }
48
49     @Override
50     public void operationComplete(final IoReadFuture future) {
51         if (checkDisconnect(future)) {
52             invokeDisconnect();
53         }
54     }
55
56     private synchronized boolean checkDisconnect(final IoReadFuture future) {
57         if (future.getException() != null) {
58             //if asyncout is already set to null by close method, do nothing
59             if (asyncOut == null) {
60                 return false;
61             }
62
63             if (asyncOut.isClosed() || asyncOut.isClosing()) {
64                 // Ssh dropped
65                 LOG.debug("Ssh session dropped on channel: {}", channelId, future.getException());
66             } else {
67                 LOG.warn("Exception while reading from SSH remote on channel {}", channelId, future.getException());
68             }
69             return true;
70         } else if (future.getRead() > 0) {
71             final ByteBuf msg = Unpooled.wrappedBuffer(buf.array(), 0, future.getRead());
72             if (LOG.isTraceEnabled()) {
73                 LOG.trace("Reading message on channel: {}, message: {}",
74                         channelId, AsyncSshHandlerWriter.byteBufToString(msg));
75             }
76             readHandler.onMessageRead(msg);
77
78             // Schedule next read
79             buf = new ByteArrayBuffer(BUFFER_SIZE);
80             currentReadFuture = asyncOut.read(buf);
81             currentReadFuture.addListener(this);
82         }
83         return false;
84     }
85
86     /**
87      * Closing of the {@link AsyncSshHandlerReader}. This method should never be called with any locks held since
88      * call to {@link AutoCloseable#close()} can be a source of ABBA deadlock.
89      */
90     @SuppressWarnings("checkstyle:IllegalCatch")
91     private void invokeDisconnect() {
92         try {
93             connectionClosedCallback.close();
94         } catch (final Exception e) {
95             // This should not happen
96             throw new IllegalStateException(e);
97         }
98     }
99
100     @Override
101     public synchronized void close() {
102         // Remove self as listener on close to prevent reading from closed input
103         if (currentReadFuture != null) {
104             currentReadFuture.removeListener(this);
105             currentReadFuture = null;
106         }
107
108         asyncOut = null;
109     }
110
111     public interface ReadMsgHandler {
112
113         void onMessageRead(ByteBuf msg);
114     }
115 }