Synchronous treeUtils.getSubtree() deadlock

Synchronous treeUtils.getSubtree(target, oid) method in snmp4j, seems to have bug in its internal synchronization implementation. In case of sending request resulting in exception e.g. timeout or credentials mismatch, the method hangs indefinitely on listener.wait().

Caused by logic in TreeUtils.walk(Target<?> target, OID[] rootOIDs): notify() method (invoked by TreeUtils.TreeRequest.send(), invoked by walk(target, rootOIDs, null, listener)) is called before wait().

Hi Matee,

Which version of SNMP4J are you using? At least for the latest version, your analysis does not seem to be true (notify is never called before wait because of synchronisation).

Best regards,
Frank

I am using snmp4j in version 3.4.0.

See example below, the async version of walk fails with exception, but the sync one hangs:

package com.example;

import org.snmp4j.*;
import org.snmp4j.mp.*;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.*;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CountDownLatch;

public class Main {

    static Snmp snmp() {
        try {
            MessageDispatcherImpl messageDispatcher = new MessageDispatcherImpl();
            messageDispatcher.addMessageProcessingModel(new MPv3());
            ThreadPool threadPool = ThreadPool.create("Thread pool", 2);
            Snmp manager = new Snmp(new MultiThreadedMessageDispatcher(threadPool, messageDispatcher), new DefaultUdpTransportMapping(new UdpAddress("0.0.0.0/8765")));
            manager.listen();
            return manager;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    static UserTarget<Address> createTargetV3() {
        UserTarget<Address> target = new UserTarget<>();
        target.setTimeout(5000);
        target.setRetries(1);
        target.setAddress(GenericAddress.parse("udp:10.1.233.120/161"));
        target.setVersion(SnmpConstants.version3);
        target.setSecurityModel(org.snmp4j.security.SecurityModel.SECURITY_MODEL_USM);
        target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
        target.setSecurityName(new OctetString("Test"));
        return target;
    }

    static List<TreeEvent> walk(TreeUtils treeUtils, Target<Address> targetV3, OID oid){
        List<TreeEvent> walkResult = new LinkedList<>();
        InternalTreeListener treeListener = new InternalTreeListener(walkResult);

        treeUtils.getSubtree(targetV3, oid, null, treeListener);
        try {
            treeListener.waitForResult();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return walkResult;
    }

    public static void main(String[] args) {

        TreeUtils treeUtils = new TreeUtils(snmp(), new DefaultPDUFactory());
        UserTarget<Address> targetV3 = createTargetV3();

        System.out.println("Async: ");
        System.out.println(walk(treeUtils, targetV3, new OID ("1.3.6.1.2.1.1")));
        System.out.println("finished");

        System.out.println("Sync: ");

        System.out.println(treeUtils.getSubtree(targetV3, new OID ("1.3.6.1.2.1.1")));
        System.out.println("finished");
    }

    static class InternalTreeListener implements TreeListener {

        private final List<TreeEvent> collectedEvents;
        private final CountDownLatch latch = new CountDownLatch(1);

        public InternalTreeListener(List<TreeEvent> eventList) {
            collectedEvents = eventList;
        }

        @Override
        public synchronized boolean next(TreeEvent event) {
            collectedEvents.add(event);
            return true;
        }

        @Override
        public synchronized void finished(TreeEvent event) {
            collectedEvents.add(event);
            latch.countDown();
        }

        @Override
        public boolean isFinished() {
            return latch.getCount() == 0;
        }

        void waitForResult() throws InterruptedException {
            latch.await();
        }
    }
}

Meanwhile I think I understand the problem better. The critical part is not after the first SNMP message has been sent by TreeUtils but before that. If in the preparation, an IOException is being thrown, the walk operation enters the wait even though the walk is already finished.

I have fixed that behaviour in the latest 3.4.3-SNAPSHOT available here:
https://snmp.app/dist/snapshot/org/snmp4j/snmp4j/3.4.3-SNAPSHOT/snmp4j-3.4.3-20200803.225225-2.jar

Hello Frank,

I am experiencing an issue that looks like the same: lots of my threads are stuck on org.snmp4j.util.TableUtils.getTable(TableUtils.java:127). I am using SNMP4J 3.2.1

“MyThread started at 2020-09-18 16:10:00-pool-29785-thread-13” #3382591 prio=5 os_prio=0 tid=0x00007f9b1c091800 nid=0x14f96 in Object.wait() [0x00007f98cff3b000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(java.base@9.0.4/Native Method)
- waiting on
at java.lang.Object.wait(java.base@9.0.4/Object.java:516)
at org.snmp4j.util.TableUtils.getTable(TableUtils.java:127)
- waiting to re-lock in wait() <0x000000043a5f7580> (a org.snmp4j.util.TableUtils$InternalTableListener)

I am going to test the 3.4.3-SNAPSHOT version as well.