lextudio/sharpsnmplib

WalkAsync is missing a CancellationToken

mjanulaitis opened this issue · 5 comments

There is no way to cancel a walk because the call does not take a CancellationToken.

lextm commented

Duplicate to #74

Hint: GitHub does allow you to search previous issues/discussions.

Issue #74 does not address the missing CancellationToken.

Async functions should always take and implement CancellationTokens. There is no way to cancel an async task externally. This must be implemented in the library.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/cancel-async-tasks-after-a-period-of-time

https://devblogs.microsoft.com/premier-developer/recommended-patterns-for-cancellationtoken/

lextm commented

It makes no sense for #SNMP Library to support cancellation tokens when the underlying socket API doesn't, and no plan to address that.

"This must be implemented in the library" is not applicable.

The time out for the HasNext is not the issue. Certainly the timeout for one variable get can be the cancellation resolution. By adding a cancellation token to the call you can add the cancellation check between each HasNext call. Thus you can cancel then get a true cancellation within the timeout specified. For example:

    public static int Walk(VersionCode version, IPEndPoint endpoint, OctetString community, ObjectIdentifier table, IList<Variable> list, int timeout, WalkMode mode, CancellationToken cancellationToken)
    {
        if (list == null)
        {
            throw new ArgumentNullException(nameof(list));
        }

        var result = 0;
        var tableV = new Variable(table);
        Variable seed;
        var next = tableV;
        var rowMask = string.Format(CultureInfo.InvariantCulture, "{0}.1.1.", table);
        var subTreeMask = string.Format(CultureInfo.InvariantCulture, "{0}.", table);
        do
        {
            cancellationToken.ThrowIfCancellationRequested();

            seed = next;
            if (seed == tableV)
            {
                continue;
            }

            if (mode == WalkMode.WithinSubtree && !seed.Id.ToString().StartsWith(subTreeMask, StringComparison.Ordinal))
            {
                // not in sub tree
                break;
            }

            list.Add(seed);
            if (seed.Id.ToString().StartsWith(rowMask, StringComparison.Ordinal))
            {
                result++;
            }
        }
        while (HasNext(version, endpoint, community, seed, timeout, out next));
        return result;
    }

For those that are interested here is the code that can be copied into your SNMP controller enabling the walk to be canceled when for example your SNMP Browser view is closed:

    public static int Walk(VersionCode version, IPEndPoint endpoint, OctetString community, ObjectIdentifier table, IList<Variable> list, int timeout, WalkMode mode, CancellationToken cancellationToken)
    {
        if (list == null)
        {
            throw new ArgumentNullException(nameof(list));
        }

        var result = 0;
        var tableV = new Variable(table);
        Variable seed;
        var next = tableV;
        var rowMask = string.Format(CultureInfo.InvariantCulture, "{0}.1.1.", table);
        var subTreeMask = string.Format(CultureInfo.InvariantCulture, "{0}.", table);
        do
        {
            cancellationToken.ThrowIfCancellationRequested();

            seed = next;
            if (seed == tableV)
            {
                continue;
            }

            if (mode == WalkMode.WithinSubtree && !seed.Id.ToString().StartsWith(subTreeMask, StringComparison.Ordinal))
            {
                // not in sub tree
                break;
            }

            list.Add(seed);
            if (seed.Id.ToString().StartsWith(rowMask, StringComparison.Ordinal))
            {
                result++;
            }
        }
        while (HasNext(version, endpoint, community, seed, timeout, out next));
        return result;
    }

    private static bool HasNext(VersionCode version, IPEndPoint endpoint, OctetString community, Variable seed, int timeout, out Variable next)
    {
        if (seed == null)
        {
            throw new ArgumentNullException(nameof(seed));
        }

        var variables = new List<Variable> { new Variable(seed.Id) };
        var message = new GetNextRequestMessage(
            Messenger.NextRequestId,
            version,
            community,
            variables);

        var response = message.GetResponse(timeout, endpoint);
        var pdu = response.Pdu();
        var errorFound = pdu.ErrorStatus.ToErrorCode() == ErrorCode.NoSuchName;
        next = errorFound ? null : pdu.Variables[0];
        return !errorFound;
    }