amittleider / AutoFinance.Broker

A Dotnet Core library to interact with Interactive Broker's Trader Workstation (IB TWS)
32 stars 16 forks source link

TaskCanceledException when calling TwsOpenOrdersController RequestOpenOrders() more than once #12

Closed dylinmaust closed 4 years ago

dylinmaust commented 4 years ago

Thank you for your work on this great library!

Is there a recommended pattern to follow when polling for events? I've configured a dotnet core Background Service to poll TWS - the first call to something like await openOrdersController.RequestOpenOrders(); successfully returns data. But subsequent calls throw TaskCanceledException every time.

I've tried both registering TwsObjectFactory and TwsConnectionController in Autofac to inject them in a class manually constructing them up for each poll iteration. Both results in this behavior.

public async Task ListenAsync(CancellationToken cancellationToken)
{
    var twsObjectFactory = new TwsObjectFactory();
    var connectionController = new TwsConnectionController(twsObjectFactory.ClientSocket, twsObjectFactory.TwsCallbackHandler, "127.0.0.1", 7462, 1);
    var openOrdersController = new TwsOpenOrdersController(twsObjectFactory.ClientSocket, twsObjectFactory.TwsCallbackHandler);

    await connectionController.EnsureConnectedAsync();
    var openOrders = await openOrdersController.RequestOpenOrders(); // <-- Throws TaskCanceledException on subsequent calls
}

https://github.com/amittleider/AutoFinance.Broker/blob/37e230cc5f9da40c4868874f7505c07a143aa31b/AutoFinance.Broker/InteractiveBrokers/Controllers/TwsOpenOrdersController.cs#L66

amittleider commented 4 years ago

Hi Dylan!

I made an attempt for a fix. I was not able to repro the issue (simply calling the controller twice), but I found a line of code that looks a bit funny, so I made an attempt at a fix.

Please give this branch a try and let me know if it works: https://github.com/amittleider/AutoFinance.Broker/pull/13/files .

As far as a recommended pattern to follow for polling, if It were me, I'd spin up a new thread using some code like the following, and put a sleep in there and whatever logic you want. However, maybe you want to use event based functions? In that case, you can use the TwsEventHandler in the lib and attach any function you want to any event. I put a small example of that below as well.

                 this.readerThread = new Thread(
                 () =>
                 {
                     while (true)
                     {
                         this.signal.waitForSignal();
                         reader.processMsgs();
                     }
                 })
                { IsBackground = true };
                this.readerThread.Start();

Event handler example

            openOrderEndEventHandler = (sender, args) =>
            {
                // Your logic here
            };

            this.twsCallbackHandler.OpenOrderEvent += openOrderEventHandler;
dylinmaust commented 4 years ago

Thanks for the quick response.

I created a console app but am unable reproduce the issue while referencing the current nuget package:

Program.cs:

static async Task Main(string[] args)
{
    var twsObjectFactory = new TwsObjectFactory();
    var connectionController = new TwsConnectionController(twsObjectFactory.ClientSocket, twsObjectFactory.TwsCallbackHandler, "127.0.0.1", 7462, 1);
    var openOrdersController = new TwsOpenOrdersController(twsObjectFactory.ClientSocket, twsObjectFactory.TwsCallbackHandler);

    await connectionController.EnsureConnectedAsync();
    var openOrders = await openOrdersController.RequestOpenOrders();
    var secondOrders = await openOrdersController.RequestOpenOrders();
    var thirdOrders = await openOrdersController.RequestOpenOrders();
}

csproj:

<PackageReference Include="AutoFinance.Broker" Version="1.0.412" />

I've since pulled down the for_dylan branch and tried referencing it directly in a dotnet core controller endpoint:

public async Task<IActionResult> TestOrders()
{
    var twsObjectFactory = new TwsObjectFactory();
    var connectionController = new TwsConnectionController(twsObjectFactory.ClientSocket, twsObjectFactory.TwsCallbackHandler, "127.0.0.1", 7462, 1);
    var openOrdersController = new TwsOpenOrdersController(twsObjectFactory.ClientSocket, twsObjectFactory.TwsCallbackHandler);

    await connectionController.EnsureConnectedAsync();
    var openOrders = await openOrdersController.RequestOpenOrders();

    return Ok();
}

Again, it exhibits the behavior described above - the first execution succeeds but subsequent calls to that endpoint result in a TaskCanceledException! I'm curious if this has something to do with the context in which the TWS code is running? It seems to be fine in a console application, but fails in a web application?

amittleider commented 4 years ago

Hi Dylin, apologies for misspelling your name before.

I don't think the problem is the context, but instead, it's the time between calls. In your web application, there is more than 5 seconds between calls (the amount of time to cancel the task), but in your console application, you call twice within 5 seconds, so the task is not cancelled.

I was able to repro the issue by putting the 5 second sleep in the test, so I feel pretty confident that the TrySetCancelled fix works. Is it possible that you were still using the old version of the code?

I have fixed the bug and published a new nuget package, version 1.0.435, can you try it? If it's still not working, please provide me with a code sample to repro.

dylinmaust commented 4 years ago

No problem!

I've confirmed that the latest package working as expected. I think the issue was related to debugging and the 5 second timeout, potentially my Autofac configuration as well as it was potentially recreating TwsObjectFactory and all the Controller services instead of reusing single instances. I've configured the background service to run with some logging and all services have been successful. Thank you for your help.

As a heads up, I will likely submit PRs for TWS APIs that are not covered under this library that are required for my app - features like:

And potentially others. Please let me know if you foresee any issues or have any guidance. It seems like they'd be pretty straightforward to implement.

Thanks again for your help!

amittleider commented 4 years ago

You're PRs are welcome, I'd be happy to read them.