dotnet / WatsonTcp

WatsonTcp is the easiest way to build TCP-based clients and servers in C#.
MIT License
600 stars 117 forks source link

System.TimeoutException when many tasks are spawned in the host application #248

Closed fisherman6v6 closed 11 months ago

fisherman6v6 commented 1 year ago

`

static void Foo()
{
  int i = 0;
  while (true)
  {
    i++;
  }
}

static void Main(string[] args)
{
  WatsonTcpServer server = new WatsonTcpServer("0.0.0.0", 9999);
  server.Events.MessageReceived += (sender, e) => { };
  server.Callbacks.SyncRequestReceived += (SyncRequest req) => new SyncResponse(req, req.Data);
  server.Start();
  string msg = new string(Enumerable.Repeat('a', 10000).ToArray());

  for (int i = 0; i < 10; i++)
  {
    Task.Run(Foo);
  }

  WatsonTcpClient client = new WatsonTcpClient("127.0.0.1", 9999);
  client.Events.MessageReceived += (sender, e) => { };
  client.Connect();
  int n = 0;
  while (true)
  {
    client.SendAndWait(1000, msg);
    Console.WriteLine($"reply received {n++}");
    Thread.Sleep(100);
  }
}

`

System.TimeoutException is thrown when many tasks are spawned in the host application before a client connect followed by a SendAndWait with a timeout of 1000 ms.

The code above will throw a System.TimeoutException every run when the application is compiled in net48 but it will throw in a non-deterministic way when compiled in net6.0. The reason is probably related to the massive use of Tasksand Task.Run inside WatsonTcp library and the different way some functions behave under the hood in net6.0 w.r.t. net48.

What about managing client-side sync requests without relying on asynchronous events in the SendAndWait function, maybe performing a Receive after the Send?

Stephen Cleary wrote an interesting article about Task.Run https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html.

jchristn commented 1 year ago

I appreciate the reference on how to do this better - but is this an actual issue or something that only happens when you intentionally try to create the issue? Cheers

fisherman6v6 commented 1 year ago

Sorry if my previous post was a bit rude, I didn't wanted to. Of course the code I posted is intentionally forcing the error but at first I noticed it in a real application where I'm using WatsonTcp and I couldn't understand why sometimes the sync message was expiring and sometimes not. The application is spawning multiple tasks before initializing the client and so the issue appears. However moving the client initialization before the tasks spawning solves the issue.

The thing is, should the hosting application threading interfere with the library behavior in a non-deterministic way?

Thank you for your time and effort