Bug 7025 - Multiple issues with concurrent WebClient calls post Mono for Android 4.2.5
Summary: Multiple issues with concurrent WebClient calls post Mono for Android 4.2.5
Status: RESOLVED DUPLICATE of bug 7200
Alias: None
Product: Android
Classification: Xamarin
Component: BCL Class Libraries ()
Version: 4.2.x
Hardware: PC Windows
: --- normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2012-09-10 04:26 UTC by Patrick
Modified: 2012-11-17 22:20 UTC (History)
6 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Sample code to reproduce the bug and output showing the bugs (726.60 KB, application/x-zip-compressed)
2012-09-11 22:58 UTC, Patrick
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED DUPLICATE of bug 7200

Description Patrick 2012-09-10 04:26:34 UTC
After upgrading to Mono for Android 4.2.5, there have been several issues with the WebClient object when making concurrent calls to DownloadStringAsync

1. The result is returned for the wrong call.
e.g. if concurrent calls to URI-A & URI-B, the result for URI-A is returned for the URI-B's call

2. Very easy to get timeout, even with very small data locally
- When using Fiddler as a HTTP proxy, response retrieved from Fiddler is successful, but the WebClient will still get a timeout



Below is the code used to make the calls

private void Request (string path, AsyncCallback callback, object userState)
{
    try
    {
        string uri = this.FOWAUri.ToString();
        if (uri[uri.Length - 1] != '/')
            uri += "/";

        uri += path;
        uri = AddRandomQuerystring(uri);

        WebClient webClient = new WebClient();
        webClient.Encoding = Encoding.UTF8;
        this.SetCredentials(webClient);
					
        webClient.DownloadStringCompleted += (sender, e) => 
        {
	    if (e.Cancelled || e.Error != null)
            {	
                // Failed
		AsyncConnectionResult acr = new AsyncConnectionResult(null, e.UserState);
		acr.Status = HttpStatusCode.Forbidden;
		callback(acr);
	    }
	    else
	    {
	        // Successful
		AsyncConnectionResult acr = new AsyncConnectionResult(null, e.UserState);
 	        acr.Status = HttpStatusCode.OK;
		acr.ResultString = (string)e.Result;
		callback(acr);
	    }
	};
					
        webClient.DownloadStringAsync(new Uri(uri), userState);
    }
    catch(System.Net.WebException e)
    {
        Thread t = new Thread(()=>{
	// Some errors
	AsyncConnectionResult acr = new AsyncConnectionResult(null, userState);
	HttpWebResponse response = e.Response as HttpWebResponse;
	acr.Status = response.StatusCode;
	callback(acr);
	});
        t.Start();
    }
    catch (Exception)
    {   }
}
Comment 1 Atsushi Eno 2012-09-10 11:55:48 UTC
For ease of repro case, I created a full runnable sample based on above, accessing this bugzilla (https://bugzilla.xamarin.com). This would show us kind of obvious output from the requests on the console output (android log).

And I couldn't reproduce this with our latest internal master.

--------
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace App7025
{
	[Activity (Label = "7025", MainLauncher = true)]
	public class Activity1 : Activity
	{
		int count = 1;

		protected override void OnCreate (Bundle bundle)
		{
			base.OnCreate (bundle);

			// Set our view from the "main" layout resource
			SetContentView (Resource.Layout.Main);

			// Get our button from the layout resource,
			// and attach an event to it
			Button button = FindViewById<Button> (Resource.Id.myButton);
			
			button.Click += delegate {
				for (int i = 0; i < 10; i++) {
					var x = rnd.Next (7000);
					Request ("?id=" + x, (ar) => {
						var res = (AsyncConnectionResult) ar;
						Console.WriteLine ("{0}: {1} -> {2}: {3}", i, x, res.Status, res.ResultString.Substring (0, 200));
						}, null);
				}
				button.Text = string.Format ("{0} clicks!", count++);
			};
		}

		private void Request (string path, AsyncCallback callback, object userState)
		{
			try
			{
				string uri = "https://bugzilla.xamarin.com/show_bug.cgi";//this.FOWAUri.ToString();
				if (uri[uri.Length - 1] != '/')
					uri += "/";
				
				uri += path;
				uri = AddRandomQuerystring(uri);
				
				WebClient webClient = new WebClient();
				webClient.Encoding = Encoding.UTF8;
				this.SetCredentials(webClient);
				
				webClient.DownloadStringCompleted += (sender, e) => 
				{
					if (e.Cancelled || e.Error != null)
					{    
						// Failed
						AsyncConnectionResult acr = new AsyncConnectionResult(null,
						                                                      e.UserState);
						acr.Status = HttpStatusCode.Forbidden;
						callback(acr);
					}
					else
					{
						// Successful
						AsyncConnectionResult acr = new AsyncConnectionResult(null,
						                                                      e.UserState);
						acr.Status = HttpStatusCode.OK;
						acr.ResultString = (string)e.Result;
						callback(acr);
					}
				};
				
				webClient.DownloadStringAsync(new Uri(uri), userState);
			}
			catch(System.Net.WebException e)
			{
				Thread t = new Thread(()=>{
					// Some errors
					AsyncConnectionResult acr = new AsyncConnectionResult(null, userState);
					HttpWebResponse response = e.Response as HttpWebResponse;
					acr.Status = response.StatusCode;
					callback(acr);
				});
				t.Start();
			}
			catch (Exception)
			{   }
		}

		ICredentials credentials;
		void SetCredentials (WebClient client)
		{
			// ???
			ServicePointManager.ServerCertificateValidationCallback += delegate(
				object sender,
				X509Certificate certificate,
				X509Chain chain,
				SslPolicyErrors sslPolicyErrors) {
				return true;
			};
		}

		Random rnd = new Random ();
		string AddRandomQuerystring (string src)
		{
			return src;
		}
	}

	class AsyncConnectionResult : IAsyncResult
	{
		public AsyncConnectionResult (object o, object us)
		{
			AsyncState = us;
		}

		public HttpStatusCode Status { get; set; }
		public string ResultString { get; set; }

		public object AsyncState { get; set; }
		public WaitHandle AsyncWaitHandle { get; set; }
		public bool CompletedSynchronously { get; set; }
		public bool IsCompleted { get; set; }
	}
}
Comment 2 Jonathan Pryor 2012-09-11 17:27:28 UTC
I can't reproduce with 4.2.5 final (4.2.5.264184895). I'm using Eno's code from Comment #1, with the following ("fixed") Click event handler:

>	button.Click += delegate {
>		for (int i = 0; i < 10; i++) {
>			var _i = i;
>			var x = rnd.Next (7000);
>			Request ("?id=" + x, (ar) => {
>				var res = (AsyncConnectionResult) ar;
>				Console.WriteLine ("Loop {0}: Bug {1} -> {2}: {3}", _i, x, res.Status, res.ResultString.Substring (0, 200));
>			}, null);
>		}
>		button.Text = string.Format ("{0} clicks!", count++);
>	};

The only change is to prevent the `i` variable from being captured in the closure. Which leads us to the output:

> I/mono-stdout(15760): Loop 3: Bug 1747 -> OK: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
> I/mono-stdout(15760):                       "http://www.w3.org/TR/html4/loose.dtd">
> I/mono-stdout(15760): <html>
> I/mono-stdout(15760):   <head>
> I/mono-stdout(15760):     <title>Bug 1747 &ndash; [New Resolver] Code completion 
> I/mono-stdout(15760): Loop 6: Bug 5992 -> OK: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
> I/mono-stdout(15760):                       "http://www.w3.org/TR/html4/loose.dtd">
> I/mono-stdout(15760): <html>
> I/mono-stdout(15760):   <head>
> I/mono-stdout(15760):     <title>Bug 5992 &ndash; Nullable type is not recognized
> I/mono-stdout(15760): Loop 4: Bug 5412 -> OK: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
> I/mono-stdout(15760):                       "http://www.w3.org/TR/html4/loose.dtd">
> I/mono-stdout(15760): <html>
> I/mono-stdout(15760):   <head>
> I/mono-stdout(15760):     <title>Bug 5412 &ndash; \t inserted when tabbing under 
...

Returning to the original bug:

> 1. The result is returned for the wrong call. e.g. if concurrent calls to 
> URI-A & URI-B, the result for URI-A is returned for the URI-B's call

Your description doesn't match your e.g., at least to my mind; "The result is returned for the wrong call" doesn't imply ordering to me, it implies that your request for URI-A got the content for URI-B.

As per the above output, we see that (1) Request for URI-A gets the content for URI A ("Bug 5412" gets content "<title>Bug 5412"), and we also see your "e.g." behavior: the results are UNORDERED, hitting URI 3 first, then URI 6, then URI 4. This is BY DESIGN:

    http://msdn.microsoft.com/en-us/library/ms144202(VS.80).aspx
> The resource is downloaded asynchronously using thread resources
> that are automatically allocated from the thread pool.

The ThreadPool is involved; ALL BETS ARE OFF when it comes to ordering, as any ThreadPool thread can execute any task at any time, especially since since a given task can take an unknown amount of time.

For example, imagine you have a ThreadPool with 2 threads, and a web site with the "funny" feature that the "file" is the number of seconds it'll take to download the file: http://example.com/4 will take 4 seconds to download, http://example.com/10 will take 10 seconds to download, etc.

Now imagine that you do:

    // Request order: 1, 10, 3
    ThreadPool.QueueUserWorkItem (_ => download http://example.com/1 );
    ThreadPool.QueueUserWorkItem (_ => download http://example.com/10 );
    ThreadPool.QueueUserWorkItem (_ => download http://example.com/3 );

With two threads, Thread1 may take http://example.com/1, Thread2 will take http://example.com/10, and then Thread1 completes http://example.com/1, at which point it executes http://example.com/3, which completes, and then finally http://example.com/10 completes:

    # completion order:
    http://example.com/1
    http://example.com/3
    http://example.com/10

It's trivial to imagine scenarios in which completion order will not match request order, for which there are several imaginable workarounds:

    http://msdn.microsoft.com/en-us/magazine/dd419664.aspx

> 2. Very easy to get timeout, even with very small data locally
> - When using Fiddler as a HTTP proxy, response retrieved from Fiddler is
> successful, but the WebClient will still get a timeout

Unfortunately I don't know how to reproduce that. :-( Eno's test case doesn't suffer from this problem...

Can you provide an example that demonstrates the timeout issue?
Comment 3 Patrick 2012-09-11 22:57:15 UTC
Hi Jonathan,

Please allow me to describe the issues that I'm facing in more detail.

In summary, my application will retrieve a few information from a WCF service upon a successful user login. For that, I request for these information:
- FarmName
- A list of location data that's in JSON format
- A list of other data that's also in JSON format.

> 1. The result is returned for the wrong call. e.g. if concurrent calls to 
> URI-A & URI-B, the result for URI-A is returned for the URI-B's call

The issue that I faced was, in both the response for the FullName and Status, sometimes both will return me the Status.

If the below is the expected response:
FarmName: TestFarm
Locations: 
[
  {
    "Id": "28719739-08b8-4a12-a751-a27ac53ac529",
    "Name": "Demo Farm",
    "Type": "Farm",
    "Children": [
      {
        "Id": "28719739-08b8-4a12-a751-a27ac53ac529/0",
        "Name": "House 1",
        "Type": "House"
      },
      {
        "Id": "28719739-08b8-4a12-a751-a27ac53ac529/1",
        "Name": "House 2",
        "Type": "House"
      }
    ]
  }
]

I will get these response instead:
FarmName: TestFarm
Locations: TestFarm

This is a very weird behavior and it happens randomly.

The way I tested it is that I passed the "ID" into the WebClient as the userState, and I used it back in the DownloadStringCompleted.

I noticed that in your sample codes, you pass null as the userState.

> 2. Very easy to get timeout, even with very small data locally
> - When using Fiddler as a HTTP proxy, response retrieved from Fiddler is
> successful, but the WebClient will still get a timeout

For the same calls above, sometimes one of the three calls will hit a timeout. I can't really say for sure which one because it could be any one of the three.

To use Fiddler, I started the emulator using this command
emulator.exe -avd MonoForAndroid_API_14 -http-proxy 192.168.117.60:9999 (replace 9999 with port from fiddler)


For the above two issues, I get it very easily when I made the calls to my WCF but I can't reproduce them at all if I made calls to the Xamarin Bugzilla site.



To assist you in your debugging, I have attached the sample code I used to replicate the bug, as well as the Fiddler & screenshot of the output

Hope you can now reproduce the errors that I am facing here.

Thanks for all the assistance.
Comment 4 Patrick 2012-09-11 22:58:49 UTC
Created attachment 2506 [details]
Sample code to reproduce the bug and output showing the bugs

Added the sample code and output with error.
I have put some comments to the screenshot to ease your debugging
Comment 5 Patrick 2012-09-11 22:59:21 UTC
Reset status to New
Comment 6 Jonathan Pryor 2012-11-16 23:29:51 UTC
Do you see this bug on Mono for Android 4.2.7? This bug sounds like a duplicate of Bug #7200, which was fixed in 4.2.7:

http://docs.xamarin.com/android/releases/Mono_For_Android_4/Mono_for_Android_4.2.7#Bug_fixes
Comment 7 Jared Reed 2012-11-17 17:42:57 UTC
I believe it iss a duplicate. It isn't a problem for me after getting 4.2.7.
Comment 8 Jonathan Pryor 2012-11-17 22:20:09 UTC
Closing as a dup, as per Comment #7.

*** This bug has been marked as a duplicate of bug 7200 ***