BUG-6470 Fix deadlock in BGP session creation 82/44382/1
authorKevin Wang <kevixw@gmail.com>
Thu, 11 Aug 2016 22:23:51 +0000 (15:23 -0700)
committerMilos Fabian <milfabia@cisco.com>
Fri, 19 Aug 2016 08:34:14 +0000 (08:34 +0000)
- Fix intermittent error in BGPDispatcherImplTest. In AbstractBGPSessionNegotiator,
  handleMessage() was able to be invoked by channel.pipeline().replace(..) recursively.
  So when handling a BGP message, it could happen that this.state gets out of sync
  and exception got thrown.

Change-Id: I346ab56fc85d1705099a69084cb89d660d8e1f61
Signed-off-by: Kevin Wang <kevixw@gmail.com>
(cherry picked from commit e06d8be10658d9a4d40b10a8d2fa324052aa2d05)

bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/AbstractBGPSessionNegotiator.java
bgp/rib-impl/src/main/java/org/opendaylight/protocol/bgp/rib/impl/BGPSessionImpl.java
bgp/rib-impl/src/test/java/org/opendaylight/protocol/bgp/rib/impl/BGPDispatcherImplTest.java
bgp/rib-impl/src/test/java/org/opendaylight/protocol/bgp/rib/impl/BGPSessionImplTest.java
bgp/rib-impl/src/test/java/org/opendaylight/protocol/bgp/rib/impl/PeerTest.java
testtool-util/src/main/java/org/opendaylight/protocol/util/InetSocketAddressUtil.java

index 121ddedb3f38e191131207a3f03a7d816d409484..596c1a1ba6daa552983b6942ece425519448e122 100644 (file)
@@ -77,7 +77,6 @@ abstract class AbstractBGPSessionNegotiator extends ChannelInboundHandlerAdapter
     private final Channel channel;
     @GuardedBy("this")
     private State state = State.IDLE;
-
     @GuardedBy("this")
     private BGPSessionImpl session;
 
@@ -117,10 +116,13 @@ abstract class AbstractBGPSessionNegotiator extends ChannelInboundHandlerAdapter
                 this.channel.eventLoop().schedule(new Runnable() {
                     @Override
                     public void run() {
-                        if (AbstractBGPSessionNegotiator.this.state != State.FINISHED) {
-                            AbstractBGPSessionNegotiator.this.sendMessage(buildErrorNotify(BGPError.HOLD_TIMER_EXPIRED));
-                            negotiationFailed(new BGPDocumentedException("HoldTimer expired", BGPError.FSM_ERROR));
-                            AbstractBGPSessionNegotiator.this.state = State.FINISHED;
+                        synchronized (AbstractBGPSessionNegotiator.this) {
+                            if (AbstractBGPSessionNegotiator.this.state != State.FINISHED) {
+                                AbstractBGPSessionNegotiator.this
+                                    .sendMessage(buildErrorNotify(BGPError.HOLD_TIMER_EXPIRED));
+                                negotiationFailed(new BGPDocumentedException("HoldTimer expired", BGPError.FSM_ERROR));
+                                AbstractBGPSessionNegotiator.this.state = State.FINISHED;
+                            }
                         }
                     }
                 }, INITIAL_HOLDTIMER, TimeUnit.MINUTES);
@@ -136,21 +138,20 @@ abstract class AbstractBGPSessionNegotiator extends ChannelInboundHandlerAdapter
     }
 
     protected synchronized void handleMessage(final Notification msg) {
-        LOG.debug("Channel {} handling message in state {}", this.channel, this.state);
-
+        LOG.debug("Channel {} handling message in state {}, msg: {}", this.channel, this.state, msg);
         switch (this.state) {
         case FINISHED:
             sendMessage(buildErrorNotify(BGPError.FSM_ERROR));
             return;
         case IDLE:
             // to avoid race condition when Open message was sent by the peer before startNegotiation could be executed
-           if (msg instanceof Open) {
-               startNegotiation();
-               handleOpen((Open) msg);
-               return;
-           }
+            if (msg instanceof Open) {
+                startNegotiation();
+                handleOpen((Open) msg);
+                return;
+            }
             sendMessage(buildErrorNotify(BGPError.FSM_ERROR));
-            return;
+            break;
         case OPEN_CONFIRM:
             if (msg instanceof Keepalive) {
                 negotiationSuccessful(this.session);
@@ -190,22 +191,23 @@ abstract class AbstractBGPSessionNegotiator extends ChannelInboundHandlerAdapter
         return builder.build();
     }
 
-    private void handleOpen(final Open openObj) {
+    private synchronized void handleOpen(final Open openObj) {
         final IpAddress remoteIp = getRemoteIp();
         final BGPSessionPreferences preferences = this.registry.getPeerPreferences(remoteIp);
         try {
             final BGPSessionListener peer = this.registry.getPeer(remoteIp, getSourceId(openObj, preferences), getDestinationId(openObj, preferences), openObj);
             sendMessage(new KeepaliveBuilder().build());
-            this.session = new BGPSessionImpl(peer, this.channel, openObj, preferences, this.registry);
             this.state = State.OPEN_CONFIRM;
-            LOG.debug("Channel {} moved to OpenConfirm state with remote proposal {}", this.channel, openObj);
+            this.session = new BGPSessionImpl(peer, this.channel, openObj, preferences, this.registry);
+            this.session.setChannelExtMsgCoder(openObj);
+            LOG.debug("Channel {} moved to OPEN_CONFIRM state with remote proposal {}", this.channel, openObj);
         } catch (final BGPDocumentedException e) {
             LOG.warn("Channel {} negotiation failed", this.channel, e);
             negotiationFailed(e);
         }
     }
 
-    private void negotiationFailed(final Throwable e) {
+    private synchronized void negotiationFailed(final Throwable e) {
         LOG.warn("Channel {} negotiation failed: {}", this.channel, e.getMessage());
         if (e instanceof BGPDocumentedException) {
             // although sendMessage() can also result in calling this method, it won't create a cycle. In case sendMessage() fails to
@@ -254,10 +256,10 @@ abstract class AbstractBGPSessionNegotiator extends ChannelInboundHandlerAdapter
             @Override
             public void operationComplete(final ChannelFuture f) {
                 if (!f.isSuccess()) {
-                    LOG.warn("Failed to send message {}", msg, f.cause());
+                    LOG.warn("Failed to send message {} to channel {}", msg,  AbstractBGPSessionNegotiator.this.channel, f.cause());
                     negotiationFailedCloseChannel(f.cause());
                 } else {
-                    LOG.trace("Message {} sent to socket", msg);
+                    LOG.trace("Message {} sent to channel {}", msg, AbstractBGPSessionNegotiator.this.channel);
                 }
             }
         });
index 3abc1ab406e86204d083d4bdc3d790c83ddb9569..fcb38fab03e94bd996d70870fd7d72cc8420c929 100644 (file)
@@ -148,10 +148,6 @@ public class BGPSessionImpl extends SimpleChannelInboundHandler<Notification> im
         this.keepAlive = this.holdTimerValue / KA_TO_DEADTIMER_RATIO;
         this.asNumber = AsNumberUtil.advertizedAsNumber(remoteOpen);
         this.peerRegistry = peerRegistry;
-        final boolean enableExMess = BgpExtendedMessageUtil.advertizedBgpExtendedMessageCapability(remoteOpen);
-        if (enableExMess) {
-            this.channel.pipeline().replace(BGPMessageHeaderDecoder.class, EXTENDED_MSG_DECODER, BGPMessageHeaderDecoder.getExtendedBGPMessageHeaderDecoder());
-        }
 
         final Set<TablesKey> tts = Sets.newHashSet();
         final Set<BgpTableType> tats = Sets.newHashSet();
@@ -208,6 +204,24 @@ public class BGPSessionImpl extends SimpleChannelInboundHandler<Notification> im
                 this.tableTypes, this.addPathTypes);
     }
 
+    /**
+     * Set the extend message coder for current channel
+     * The reason for separating this part from constructor is, in #channel.pipeline().replace(..), the
+     * invokeChannelRead() will be invoked after the original message coder handler got removed. And there
+     * is chance that before the session instance is fully initiated (constructor returns), a KeepAlive
+     * message arrived already in the channel buffer. Thus #AbstractBGPSessionNegotiator.handleMessage(..)
+     * gets invoked again and a deadlock is caused.  A BGP final state machine error will happen as BGP
+     * negotiator is still in OPEN_SENT state as the session constructor hasn't returned yet.
+     *
+     * @param remoteOpen
+     */
+    public synchronized void setChannelExtMsgCoder(final Open remoteOpen) {
+        final boolean enableExMess = BgpExtendedMessageUtil.advertizedBgpExtendedMessageCapability(remoteOpen);
+        if (enableExMess) {
+            this.channel.pipeline().replace(BGPMessageHeaderDecoder.class, EXTENDED_MSG_DECODER, BGPMessageHeaderDecoder.getExtendedBGPMessageHeaderDecoder());
+        }
+    }
+
     @Override
     public synchronized void close() {
         if (this.state != State.IDLE) {
index 96e80dc542715a7b25677aaccf1ddc4636fa4832..d0f3736b2b1987a107c682dac2acb8f17274e76e 100755 (executable)
@@ -58,7 +58,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.type
 import org.slf4j.LoggerFactory;
 
 public class BGPDispatcherImplTest {
-
+    private static final short HOLD_TIMER = 30;
     private static final AsNumber AS_NUMBER = new AsNumber(30L);
     private static final int RETRY_TIMER = 1;
     private static final BgpTableType IPV_4_TT = new BgpTableTypeImpl(Ipv4AddressFamily.class, UnicastSubsequentAddressFamily.class);
@@ -149,7 +149,7 @@ public class BGPDispatcherImplTest {
         Assert.assertEquals(Sets.newHashSet(IPV_4_TT), session.getAdvertisedTableTypes());
         Assert.assertTrue(serverChannel.isWritable());
         session.close();
-
+        this.serverListener.releaseConnection();
         checkIdleState(this.clientListener);
         checkIdleState(this.serverListener);
     }
@@ -178,6 +178,6 @@ public class BGPDispatcherImplTest {
         capas.add(new OptionalCapabilitiesBuilder().setCParameters(BgpExtendedMessageUtil.EXTENDED_MESSAGE_CAPABILITY).build());
         tlvs.add(new BgpParametersBuilder().setOptionalCapabilities(capas).build());
         final BgpId bgpId = new BgpId(new Ipv4Address(socketAddress.getAddress().getHostAddress()));
-        return new BGPSessionPreferences(AS_NUMBER, (short) 4, bgpId, AS_NUMBER, tlvs, Optional.absent());
+        return new BGPSessionPreferences(AS_NUMBER, HOLD_TIMER, bgpId, AS_NUMBER, tlvs, Optional.absent());
     }
 }
index 9bc5dbc6dae51458b87ed1444597d4d506570da7..a9b75bb33fa1cb88cac13a00cf48132907f34812 100644 (file)
@@ -149,6 +149,7 @@ public class BGPSessionImplTest {
         doReturn(futureChannel).when(this.speakerListener).close();
         this.listener = new SimpleSessionListener();
         this.bgpSession = new BGPSessionImpl(this.listener, this.speakerListener, this.classicOpen, this.classicOpen.getHoldTimer(), null);
+        this.bgpSession.setChannelExtMsgCoder(this.classicOpen);
     }
 
     @Test
index fa74d4557ebcc39fc70b92c082714559aeff3a64..33fcd60f49d4c01e4bdc1c250a5f1bd7db229e31 100644 (file)
@@ -30,7 +30,6 @@ import org.junit.Test;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
-import org.opendaylight.controller.config.api.IdentityAttributeRef;
 import org.opendaylight.controller.config.yang.bgp.rib.impl.RouteTable;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
@@ -41,6 +40,7 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.ipv4.routes.ipv4.routes.Ipv4Route;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.KeepaliveBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Open;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.OpenBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.UpdateBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.open.message.BgpParameters;
@@ -220,6 +220,8 @@ public class PeerTest extends AbstractRIBTestSetup {
             Lists.newArrayList(new OptionalCapabilitiesBuilder().setCParameters(new CParametersBuilder().addAugmentation(
                 CParameters1.class, new CParameters1Builder().setMultiprotocolCapability(new MultiprotocolCapabilityBuilder()
                     .setAfi(Ipv4AddressFamily.class).setSafi(UnicastSubsequentAddressFamily.class).build()).build()).build()).build())).build());
-        this.session = new BGPSessionImpl(this.classic, channel, new OpenBuilder().setBgpIdentifier(new Ipv4Address("1.1.1.1")).setHoldTimer(50).setMyAsNumber(72).setBgpParameters(params).build(), 30, null);
+        final Open openObj = new OpenBuilder().setBgpIdentifier(new Ipv4Address("1.1.1.1")).setHoldTimer(50).setMyAsNumber(72).setBgpParameters(params).build();
+        this.session = new BGPSessionImpl(this.classic, channel, openObj, 30, null);
+        this.session.setChannelExtMsgCoder(openObj);
     }
 }
index 5b977b40e28f0fbe5fea87dc10dd95f087138bee..94a67aff965bb2186090ab07e1d4c925ae048c8a 100644 (file)
@@ -55,10 +55,10 @@ public final class InetSocketAddressUtil {
     /**
      * Generate a random high range port number
      *
-     * @return A port number range from 10000 to 50000
+     * @return A port number range from 20000 to 60000
      */
     public static int getRandomPort() {
-        final int randPort = 10000 + (int) Math.round(40000 * Math.random());
+        final int randPort = 20000 + (int) Math.round(40000 * Math.random());
         return randPort;
     }