0 votes
by (130 points)

I work on a project which sends out messages on various ways. One of them is "place a file containing the message on an SFTP server behind a HTTP proxy". I use Rebex SFTP to build this functionality. To achieve improvied resilience of the overall system, I'm currently adding a uniform message sending timeout for all of my message sinks. Thus, I want to achieve

  • placing the SFTP file should never take longer than 10s
  • no matter if the SFTP connection was already open before and the client is re-used or if the file is put using a fresh Sftp instance
  • if it does take longer than 10s, a cancellation should be issued and if graceful cancellaiton is not possible, don't be graceful
  • I plan to implement this using Polly's TimeoutStrategy, which involves CancellationToken

I understand that Rebex Sftp currently doesn't have CancellationToken support, despite plans to add it for some years. How would you suggest to achieve the required funcitonality?

  • If I understand correctly, AbortTransfer e.g. wouldn't cancel ConnectAsync or LoginAsync, would it?
  • It would probably be possible to implement a wrapper around Sftp with all methods I need plus a CancellationToken parameter each. When the token is triggered it waits for the task to finish for some additional milliseconds and afterwards calls Sftp.Dispose and replaces it's internal Sftp instance with a fresh one. This seems rather non-trivial to get right though.
  • I understand it might also be possible to involve Sftp.Timeout, but achieving my goal with it seems non-trivial either. I would probably need to note when my logical "send message" op started and re-calculate the remaining Sftp.Timeout for every call to Sftp based on the time already spent.
  • Probably it would even be best to combine both approaches.
  • Do you have some better suggestion?
Applies to: Rebex SFTP

1 Answer

+1 vote
by (148k points)

We have been postponing CancellationToken support for mainly two reasons:

  • We still plan to support legacy platforms such as .NET Framework 2.0/3.5 or .NET Compact Framework 3.5/3.9 in the forthcoming version 8.0. So at this point, upgrading large parts of our codebase in order to add CancellationToken would be impractical. We will revisit this for version 9.0.

  • We plan to introduce a new fully-asynchronous SSH and SFTP core in version 9.0, which will greatly facilitate the addition of CancellationToken support.

  • We feel that the current cancellation model is flawed. Having to manually add CancellationToken argument to any async method that performs any cancelable operation seems somewhat bloated and unnecessarily complicated. We have been hoping that Microsoft adds some new C# syntax to make this streamlined, but there is still no sing of this.

  • Adding CancellationToken to Rebex SFTP might introduce a false sense of safety. Most SFTP operations are not actually cancelable, and it's not even clear what a request to cancel them is supposed to mean. A distinction between graceful and ungraceful cancelation would be very helpful indeed. Again, we have been hoping for some improvements in .NET in this area, but to no avail.

Still, we will apparently have to add CancellationToken support soon, but figuring workaround for the problems outlined above is going to be complicated. We will most likely end up with a model resembling the behavior of the current Sftp.Timeout, where canceling the token would trigger a similar mechanism.

However, in the meantime, using AbortTransfer and/or Dispose is the way to go. AbortTransfer only affects file transfers, listings, and multi-file methods, so a perhaps it would be best to call AbortTransfer(null) first to abort these, followed by Dispose() in case the active operations don't end within a second or so.

In most scenarios, I would not recommend implementing a wrapper around Sftp with all methods. I would rather create a wrapper that only exposes the SFTP functionality you need in your project, which might not necessarily have to mirror the Rebex API. If you are only going to "place a file containing the message on an SFTP server", then perhaps you would be fine with a single method that accepts all the parameters it needs (server name, fingerprint, credentials, source path and target path) along with CancellationToken, and perform cleanup using AbortTransfer/Dispose internally if it gets canceled. One we add cancellation support, you would just refactor the internals of this and keep the interfacing to the rest of your app intact.

The good news is that Rebex SFTP library is going to be the first one to get cancellation support, so hopefully the wait is not going to be too long.

by (130 points)
Thanks for the fast and detailed response! I wasn't planning to replicate the full API, indeed just an excerpt as you suggested.

I guess for a lot of stuff that currently supports CancellationToken graceful vs ungraceful cancellation and nuances in between can be tought. E.g. a REST API is left in an unknown state after cancellation too, isn't it (maybe the requested operation finished and we just cancelled receiving the response)? But this fits my requirements: If I cancelled something and an OperationCancelledException is actually thrown, I assume the operation didn't work, even though I know there's a possibility it actually did finish. I also think it would be reasonable to check e.g. IsConnected again after a transfer failed with an exception. Thus, basically "pulling the plug" and disposing the underlaying socket would be fine for my use.
by (148k points)
Yes, if you are aware of possible side-effects and all parts of the whole system can accommodate for them, "pulling the plug" by calling the Dispose method will be fine!
...