public virtual void Run() { running = true; HttpClient httpClient; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Database.Tag, "ChangeTracker run() loop aborting because client == null"); return; } if (mode == ChangeTracker.ChangeTrackerMode.Continuous) { // there is a failing unit test for this, and from looking at the code the Replication // object will never use Continuous mode anyway. Explicitly prevent its use until // it is demonstrated to actually work. throw new RuntimeException("ChangeTracker does not correctly support continuous mode" ); } httpClient = client.GetHttpClient(); ChangeTrackerBackoff backoff = new ChangeTrackerBackoff(); while (running) { Uri url = GetChangesFeedURL(); request = new HttpRequestMessage(url.ToString()); AddRequestHeaders(request); // if the URL contains user info AND if this a DefaultHttpClient // then preemptively set the auth credentials if (url.GetUserInfo() != null) { Log.V(Database.Tag, "url.getUserInfo(): " + url.GetUserInfo()); if (url.GetUserInfo().Contains(":") && !url.GetUserInfo().Trim().Equals(":")) { string[] userInfoSplit = url.GetUserInfo().Split(":"); throw new NotImplementedException(); // Credentials creds = new UsernamePasswordCredentials(URIUtils.Decode(userInfoSplit // [0]), URIUtils.Decode(userInfoSplit[1])); // if (httpClient is DefaultHttpClient) // { // DefaultHttpClient dhc = (DefaultHttpClient)httpClient; // MessageProcessingHandler preemptiveAuth = new _MessageProcessingHandler_212(creds // ); // dhc.AddRequestInterceptor((HttpWebRequest request, HttpContext context)=> // { // AuthState authState = (AuthState)context.GetAttribute(ClientContext.TargetAuthState // ); // CredentialsProvider credsProvider = (CredentialsProvider)context.GetAttribute(ClientContext // .CredsProvider); // HttpHost targetHost = (HttpHost)context.GetAttribute(ExecutionContext.HttpTargetHost // ); // if (authState.GetAuthScheme() == null) // { // AuthScope authScope = new AuthScope(targetHost.GetHostName(), targetHost.GetPort( // )); // authState.SetAuthScheme(new BasicScheme()); // authState.SetCredentials(creds); // } // }, 0); // } } else { Log.W(Database.Tag, "ChangeTracker Unable to parse user info, not setting credentials" ); } } try { string maskedRemoteWithoutCredentials = GetChangesFeedURL().ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@" , "://---:---@"); Log.V(Database.Tag, "Making request to " + maskedRemoteWithoutCredentials); HttpResponse response = httpClient.Execute(request); StatusLine status = response.GetStatusLine(); if (status.GetStatusCode() >= 300) { Log.E(Database.Tag, "Change tracker got error " + Sharpen.Extensions.ToString(status .GetStatusCode())); string msg = string.Format(status.ToString()); this.error = new CouchbaseLiteException(msg, new Status(status.GetStatusCode())); Stop(); } HttpEntity entity = response.GetEntity(); InputStream input = null; if (entity != null) { input = entity.GetContent(); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll) { IDictionary <string, object> fullBody = Manager.GetObjectMapper().ReadValue <IDictionary >(input); bool responseOK = ReceivedPollResponse(fullBody); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll && responseOK) { Log.V(Database.Tag, "Starting new longpoll"); continue; } else { Log.W(Database.Tag, "Change tracker calling stop"); Stop(); } } else { JsonFactory jsonFactory = Manager.GetObjectMapper().GetJsonFactory(); JsonParser jp = jsonFactory.CreateJsonParser(input); while (jp.CurrentToken() != JsonToken.StartArray) { } // ignore these tokens while (jp.CurrentToken() == JsonToken.StartObject) { IDictionary <string, object> change = (IDictionary)Manager.GetObjectMapper().ReadValue <IDictionary>(jp); if (!ReceivedChange(change)) { Log.W(Database.Tag, string.Format("Received unparseable change line from server: %s" , change)); } } Stop(); break; } backoff.ResetBackoff(); } } catch (Exception e) { if (!running && e is IOException) { } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Database.Tag, "Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } } Log.V(Database.Tag, "Change tracker run loop exiting"); }
public void Run() { IsRunning = true; var clientCopy = client; if (clientCopy == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(TAG, "ChangeTracker run() loop aborting because client == null"); return; } if (tokenSource.IsCancellationRequested) { tokenSource.Dispose(); tokenSource = new CancellationTokenSource(); } backoff = new ChangeTrackerBackoff(); _startTime = DateTime.Now; if (Request != null) { Request.Dispose(); Request = null; } var url = GetChangesFeedURL(); Request = new HttpRequestMessage(HttpMethod.Post, url); var body = GetChangesFeedPostBody(); Request.Content = new StringContent(body); Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); AddRequestHeaders(Request); var maskedRemoteWithoutCredentials = url.ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(TAG, "Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) { return; } HttpClient httpClient = null; try { httpClient = clientCopy.GetHttpClient(mode == ChangeTrackerMode.LongPoll); var challengeResponseAuth = Authenticator as IChallengeResponseAuthenticator; if(challengeResponseAuth != null) { challengeResponseAuth.PrepareWithRequest(Request); } var authHeader = AuthUtils.GetAuthenticationHeaderValue(Authenticator, Request.RequestUri); if (authHeader != null) { httpClient.DefaultRequestHeaders.Authorization = authHeader; } changesFeedRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); var option = mode == ChangeTrackerMode.LongPoll ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead; var info = httpClient.SendAsync( Request, option, changesFeedRequestTokenSource.Token ); info.ContinueWith(t1 => { ChangeFeedResponseHandler(t1).ContinueWith(t2 => { if(httpClient != null) { httpClient.Dispose(); } }); }, changesFeedRequestTokenSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); } catch (Exception e) { if (!IsRunning && e.InnerException is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(TAG, "Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } }
// TODO: Needs to refactored into smaller calls. Each continuation could be its own method, for example. public void Run() { IsRunning = true; var clientCopy = client; if (clientCopy == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Tag, "ChangeTracker run() loop aborting because client == null"); return; } if (tokenSource.IsCancellationRequested) { tokenSource.Dispose(); tokenSource = new CancellationTokenSource(); } backoff = new ChangeTrackerBackoff(); while (IsRunning && !tokenSource.Token.IsCancellationRequested) { _startTime = DateTime.Now; if (Request != null) { Request.Dispose(); Request = null; } var url = GetChangesFeedURL(); if (UsePost) { Request = new HttpRequestMessage(HttpMethod.Post, url); var body = GetChangesFeedPostBody(); Request.Content = new StringContent(body); Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } else { Request = new HttpRequestMessage(HttpMethod.Get, url); } AddRequestHeaders(Request); var maskedRemoteWithoutCredentials = url.ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(Tag, "Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) { break; } Task<HttpResponseMessage> changesRequestTask = null; Task successHandler; Task errorHandler; HttpClient httpClient = null; try { httpClient = clientCopy.GetHttpClient(mode == ChangeTrackerMode.LongPoll); var authHeader = AuthUtils.GetAuthenticationHeaderValue(Authenticator, Request.RequestUri); if (authHeader != null) { httpClient.DefaultRequestHeaders.Authorization = authHeader; } changesFeedRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); var option = mode == ChangeTrackerMode.LongPoll ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead; var info = httpClient.SendAsync( Request, option, changesFeedRequestTokenSource.Token ); successHandler = info.ContinueWith( ChangeFeedResponseHandler, changesFeedRequestTokenSource.Token, TaskContinuationOptions.LongRunning | TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler ); errorHandler = info.ContinueWith(t => { if (t.IsCanceled) { return; // Not a real error. } var err = t.Exception.Flatten(); Log.D(Tag, "ChangeFeedResponseHandler faulted.", err.InnerException ?? err); Error = err.InnerException ?? err; backoff.SleepAppropriateAmountOfTime(); }, changesFeedRequestTokenSource.Token, TaskContinuationOptions.NotOnRanToCompletion, WorkExecutor.Scheduler); try { Task.WaitAll(successHandler, errorHandler); Log.D(Tag, "Finished processing changes feed."); } catch (Exception ex) { var e = ex.InnerException ?? ex; // Swallow TaskCancelledExceptions, which will always happen // if either errorHandler or successHandler don't need to fire. if (!(e is OperationCanceledException)) throw ex; } finally { if (changesRequestTask != null) { if(changesRequestTask.IsCompleted) { changesRequestTask.Dispose(); } changesRequestTask = null; } if (successHandler != null) { if(successHandler.IsCompleted) { successHandler.Dispose(); } successHandler = null; } if (errorHandler != null) { if(errorHandler.IsCompleted) { errorHandler.Dispose(); } errorHandler = null; } if(Request != null) { Request.Dispose(); Request = null; } if(changesFeedRequestTokenSource != null) { changesFeedRequestTokenSource.Dispose(); changesFeedRequestTokenSource = null; } } } catch (Exception e) { if (!IsRunning && e.InnerException is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Tag, "Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } finally { if (httpClient != null) { httpClient.Dispose(); } if (mode == ChangeTrackerMode.OneShot) { Stop(); } } } }
public virtual void Run() { running = true; HttpClient httpClient; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Database.Tag, this + ": ChangeTracker run() loop aborting because client == null" ); return; } if (mode == ChangeTracker.ChangeTrackerMode.Continuous) { // there is a failing unit test for this, and from looking at the code the Replication // object will never use Continuous mode anyway. Explicitly prevent its use until // it is demonstrated to actually work. throw new RuntimeException("ChangeTracker does not correctly support continuous mode" ); } httpClient = client.GetHttpClient(); backoff = new ChangeTrackerBackoff(); while (running) { Uri url = GetChangesFeedURL(); request = new HttpGet(url.ToString()); AddRequestHeaders(request); // if the URL contains user info AND if this a DefaultHttpClient // then preemptively set the auth credentials if (url.GetUserInfo() != null) { Log.V(Database.Tag, this + ": url.getUserInfo(): " + url.GetUserInfo()); if (url.GetUserInfo().Contains(":") && !url.GetUserInfo().Trim().Equals(":")) { string[] userInfoSplit = url.GetUserInfo().Split(":"); Credentials creds = new UsernamePasswordCredentials(URIUtils.Decode(userInfoSplit [0]), URIUtils.Decode(userInfoSplit[1])); if (httpClient is DefaultHttpClient) { DefaultHttpClient dhc = (DefaultHttpClient)httpClient; MessageProcessingHandler preemptiveAuth = new _MessageProcessingHandler_221(creds ); dhc.AddRequestInterceptor(preemptiveAuth, 0); } } else { Log.W(Database.Tag, this + ": ChangeTracker Unable to parse user info, not setting credentials" ); } } try { string maskedRemoteWithoutCredentials = GetChangesFeedURL().ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@" , "://---:---@"); Log.V(Database.Tag, this + ": Making request to " + maskedRemoteWithoutCredentials ); HttpResponse response = httpClient.Execute(request); StatusLine status = response.GetStatusLine(); if (status.GetStatusCode() >= 300) { Log.E(Database.Tag, this + ": Change tracker got error " + Sharpen.Extensions.ToString (status.GetStatusCode())); string msg = string.Format(status.ToString()); this.error = new CouchbaseLiteException(msg, new Status(status.GetStatusCode())); Stop(); } HttpEntity entity = response.GetEntity(); InputStream input = null; if (entity != null) { try { input = entity.GetContent(); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll) { // continuous replications IDictionary<string, object> fullBody = Manager.GetObjectMapper().ReadValue<IDictionary >(input); bool responseOK = ReceivedPollResponse(fullBody); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll && responseOK) { Log.V(Database.Tag, this + ": Starting new longpoll"); backoff.ResetBackoff(); continue; } else { Log.W(Database.Tag, this + ": Change tracker calling stop (LongPoll)"); Stop(); } } else { // one-shot replications JsonFactory jsonFactory = Manager.GetObjectMapper().GetJsonFactory(); JsonParser jp = jsonFactory.CreateJsonParser(input); while (jp.NextToken() != JsonToken.StartArray) { } // ignore these tokens while (jp.NextToken() == JsonToken.StartObject) { IDictionary<string, object> change = (IDictionary)Manager.GetObjectMapper().ReadValue <IDictionary>(jp); if (!ReceivedChange(change)) { Log.W(Database.Tag, string.Format("Received unparseable change line from server: %s" , change)); } } Log.W(Database.Tag, this + ": Change tracker calling stop (OneShot)"); Stop(); break; } backoff.ResetBackoff(); } finally { try { entity.ConsumeContent(); } catch (IOException) { } } } } catch (Exception e) { if (!running && e is IOException) { } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Database.Tag, this + ": Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } } Log.V(Database.Tag, this + ": Change tracker run loop exiting"); }
public void Run() { IsRunning = true; var clientCopy = client; if (clientCopy == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(TAG, "ChangeTracker run() loop aborting because client == null"); return; } if (tokenSource.IsCancellationRequested) { tokenSource.Dispose(); tokenSource = new CancellationTokenSource(); } backoff = new ChangeTrackerBackoff(); _startTime = DateTime.Now; if (Request != null) { Request.Dispose(); Request = null; } var url = GetChangesFeedURL(); Request = new HttpRequestMessage(HttpMethod.Post, url); var body = GetChangesFeedPostBody(); Request.Content = new StringContent(body); Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); AddRequestHeaders(Request); var maskedRemoteWithoutCredentials = url.ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(TAG, "Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) { return; } HttpClient httpClient = null; try { httpClient = clientCopy.GetHttpClient(mode == ChangeTrackerMode.LongPoll); var challengeResponseAuth = Authenticator as IChallengeResponseAuthenticator; if (challengeResponseAuth != null) { challengeResponseAuth.PrepareWithRequest(Request); } var authHeader = AuthUtils.GetAuthenticationHeaderValue(Authenticator, Request.RequestUri); if (authHeader != null) { httpClient.DefaultRequestHeaders.Authorization = authHeader; } changesFeedRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); var option = mode == ChangeTrackerMode.LongPoll ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead; var info = httpClient.SendAsync( Request, option, changesFeedRequestTokenSource.Token ); info.ContinueWith(t1 => { ChangeFeedResponseHandler(t1).ContinueWith(t2 => { if (httpClient != null) { httpClient.Dispose(); } }); }, changesFeedRequestTokenSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default); } catch (Exception e) { if (!IsRunning && e.InnerException is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(TAG, "Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } }
// TODO: Needs to refactored into smaller calls. Each continuation could be its own method, for example. public void Run() { IsRunning = true; var clientCopy = client; if (clientCopy == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Tag, "ChangeTracker run() loop aborting because client == null"); return; } if (tokenSource.IsCancellationRequested) { tokenSource.Dispose(); tokenSource = new CancellationTokenSource(); } backoff = new ChangeTrackerBackoff(); while (IsRunning && !tokenSource.Token.IsCancellationRequested) { if (Request != null) { Request.Dispose(); Request = null; } var url = GetChangesFeedURL(); if (UsePost) { Request = new HttpRequestMessage(HttpMethod.Post, url); var body = GetChangesFeedPostBody(); Request.Content = new StringContent(body); Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } else { Request = new HttpRequestMessage(HttpMethod.Get, url); } AddRequestHeaders(Request); var maskedRemoteWithoutCredentials = url.ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(Tag, "Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) { break; } Task <HttpResponseMessage> changesRequestTask = null; Task <HttpResponseMessage> successHandler; Task <Boolean> errorHandler; HttpClient httpClient = null; try { httpClient = clientCopy.GetHttpClient(); var authHeader = AuthUtils.GetAuthenticationHeaderValue(Authenticator, Request.RequestUri); if (authHeader != null) { httpClient.DefaultRequestHeaders.Authorization = authHeader; } changesFeedRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); var evt = new ManualResetEvent(false); // We do this akward set of calls in order // to help minimize the frequency of the error: // // "Cannot re-call start of asynchronous method // while a previous call is still in progress." // // There's got to be a better way to deal with this. var info = httpClient.SendAsync( Request, changesFeedRequestTokenSource.Token ); info.ContinueWith((t) => evt.Set() ); if (evt.WaitOne(ManagerOptions.Default.RequestTimeout) == false) { Log.W(Tag, "SendAsync timeout"); continue; } changesRequestTask = info; successHandler = changesRequestTask.ContinueWith <HttpResponseMessage>( ChangeFeedResponseHandler, changesFeedRequestTokenSource.Token, TaskContinuationOptions.LongRunning | TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler ); errorHandler = changesRequestTask.ContinueWith(t => { if (t.IsCanceled) { return(false); // Not a real error. } var err = t.Exception.Flatten(); Log.D(Tag, "ChangeFeedResponseHandler faulted.", err.InnerException ?? err); Error = err.InnerException ?? err; backoff.SleepAppropriateAmountOfTime(); return(true); // a real error. }, changesFeedRequestTokenSource.Token, TaskContinuationOptions.OnlyOnFaulted, WorkExecutor.Scheduler); try { Task.WaitAll(new Task[] { successHandler, errorHandler }, (Int32)ManagerOptions.Default.RequestTimeout.TotalMilliseconds, changesFeedRequestTokenSource.Token); Log.D(Tag, "Finished processing changes feed."); } catch (Exception ex) { var e = ex.InnerException ?? ex; // Swallow TaskCancelledExceptions, which will always happen // if either errorHandler or successHandler don't need to fire. if (!(e is OperationCanceledException)) { throw ex; } } finally { if (changesRequestTask != null) { if (changesRequestTask.IsCompleted) { changesRequestTask.Dispose(); } changesRequestTask = null; } if (successHandler != null) { if (successHandler.IsCompleted) { successHandler.Dispose(); } successHandler = null; } if (errorHandler != null) { if (errorHandler.IsCompleted) { errorHandler.Dispose(); } errorHandler = null; } if (Request != null) { Request.Dispose(); Request = null; } if (changesFeedRequestTokenSource != null) { changesFeedRequestTokenSource.Dispose(); changesFeedRequestTokenSource = null; } } } catch (Exception e) { if (!IsRunning && e.InnerException is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Tag, "Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } finally { if (httpClient != null) { httpClient.Dispose(); } if (mode == ChangeTrackerMode.OneShot) { Stop(); } } } }
public virtual void Run() { running = true; HttpClient httpClient; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Log.TagChangeTracker, "%s: ChangeTracker run() loop aborting because client == null" , this); return; } if (mode == ChangeTracker.ChangeTrackerMode.Continuous) { // there is a failing unit test for this, and from looking at the code the Replication // object will never use Continuous mode anyway. Explicitly prevent its use until // it is demonstrated to actually work. throw new RuntimeException("ChangeTracker does not correctly support continuous mode" ); } httpClient = client.GetHttpClient(); backoff = new ChangeTrackerBackoff(); while (running) { Uri url = GetChangesFeedURL(); if (usePOST) { HttpPost postRequest = new HttpPost(url.ToString()); postRequest.SetHeader("Content-Type", "application/json"); StringEntity entity; try { entity = new StringEntity(ChangesFeedPOSTBody()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } postRequest.SetEntity(entity); request = postRequest; } else { request = new HttpGet(url.ToString()); } AddRequestHeaders(request); // Perform BASIC Authentication if needed bool isUrlBasedUserInfo = false; // If the URL contains user info AND if this a DefaultHttpClient then preemptively set the auth credentials string userInfo = url.GetUserInfo(); if (userInfo != null) { isUrlBasedUserInfo = true; } else { if (authenticator != null) { AuthenticatorImpl auth = (AuthenticatorImpl)authenticator; userInfo = auth.AuthUserInfo(); } } if (userInfo != null) { if (userInfo.Contains(":") && !userInfo.Trim().Equals(":")) { string[] userInfoElements = userInfo.Split(":"); string username = isUrlBasedUserInfo ? URIUtils.Decode(userInfoElements[0]) : userInfoElements [0]; string password = isUrlBasedUserInfo ? URIUtils.Decode(userInfoElements[1]) : userInfoElements [1]; Credentials credentials = new UsernamePasswordCredentials(username, password); if (httpClient is DefaultHttpClient) { DefaultHttpClient dhc = (DefaultHttpClient)httpClient; MessageProcessingHandler preemptiveAuth = new _MessageProcessingHandler_285(credentials ); dhc.AddRequestInterceptor(preemptiveAuth, 0); } } else { Log.W(Log.TagChangeTracker, "RemoteRequest Unable to parse user info, not setting credentials" ); } } try { string maskedRemoteWithoutCredentials = GetChangesFeedURL().ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@" , "://---:---@"); Log.V(Log.TagChangeTracker, "%s: Making request to %s", this, maskedRemoteWithoutCredentials ); HttpResponse response = httpClient.Execute(request); StatusLine status = response.GetStatusLine(); if (status.GetStatusCode() >= 300 && !Utils.IsTransientError(status)) { Log.E(Log.TagChangeTracker, "%s: Change tracker got error %d", this, status.GetStatusCode ()); this.error = new HttpResponseException(status.GetStatusCode(), status.GetReasonPhrase ()); Stop(); } HttpEntity entity = response.GetEntity(); InputStream input = null; if (entity != null) { try { input = entity.GetContent(); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll) { // continuous replications IDictionary<string, object> fullBody = Manager.GetObjectMapper().ReadValue<IDictionary >(input); bool responseOK = ReceivedPollResponse(fullBody); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll && responseOK) { Log.V(Log.TagChangeTracker, "%s: Starting new longpoll", this); backoff.ResetBackoff(); continue; } else { Log.W(Log.TagChangeTracker, "%s: Change tracker calling stop (LongPoll)", this); Stop(); } } else { // one-shot replications JsonFactory jsonFactory = Manager.GetObjectMapper().GetJsonFactory(); JsonParser jp = jsonFactory.CreateJsonParser(input); while (jp.NextToken() != JsonToken.StartArray) { } // ignore these tokens while (jp.NextToken() == JsonToken.StartObject) { IDictionary<string, object> change = (IDictionary)Manager.GetObjectMapper().ReadValue <IDictionary>(jp); if (!ReceivedChange(change)) { Log.W(Log.TagChangeTracker, "Received unparseable change line from server: %s", change ); } } Log.W(Log.TagChangeTracker, "%s: Change tracker calling stop (OneShot)", this); Stop(); break; } backoff.ResetBackoff(); } finally { try { entity.ConsumeContent(); } catch (IOException) { } } } } catch (Exception e) { if (!running && e is IOException) { } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Log.TagChangeTracker, this + ": Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } } Log.V(Log.TagChangeTracker, "%s: Change tracker run loop exiting", this); }
// TODO: Needs to refactored into smaller calls. Each continuation could be its own method, for example. public void Run() { IsRunning = true; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Tag, "ChangeTracker run() loop aborting because client == null"); return; } if (tokenSource.IsCancellationRequested) { tokenSource.Dispose(); tokenSource = new CancellationTokenSource(); } backoff = new ChangeTrackerBackoff(); while (IsRunning && !tokenSource.Token.IsCancellationRequested) { if (Request != null) { Request.Dispose(); Request = null; } var url = GetChangesFeedURL(); if (UsePost) { Request = new HttpRequestMessage(HttpMethod.Post, url); var body = GetChangesFeedPostBody(); Request.Content = new StringContent(body); Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } else { Request = new HttpRequestMessage(HttpMethod.Get, url); } AddRequestHeaders(Request); var maskedRemoteWithoutCredentials = url.ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(Tag, "Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) { break; } Task<HttpResponseMessage> changesRequestTask = null; Task<HttpResponseMessage> successHandler; Task<Boolean> errorHandler; HttpClient httpClient = null; try { httpClient = client.GetHttpClient(); var authHeader = AuthUtils.GetAuthenticationHeaderValue(Authenticator, Request.RequestUri); if (authHeader != null) { httpClient.DefaultRequestHeaders.Authorization = authHeader; } changesFeedRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); var evt = new ManualResetEvent(false); // We do this akward set of calls in order // to help minimize the frequency of the error: // // "Cannot re-call start of asynchronous method // while a previous call is still in progress." // // There's got to be a better way to deal with this. var info = httpClient.SendAsync( Request, changesFeedRequestTokenSource.Token ); var infoAwaiter = info.ConfigureAwait(false).GetAwaiter(); infoAwaiter.OnCompleted(()=> evt.Set() ); evt.WaitOne(ManagerOptions.Default.RequestTimeout); changesRequestTask = info; successHandler = changesRequestTask.ContinueWith<HttpResponseMessage>( ChangeFeedResponseHandler, changesFeedRequestTokenSource.Token, TaskContinuationOptions.LongRunning | TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler ); errorHandler = changesRequestTask.ContinueWith(t => { if (t.IsCanceled) { return false; // Not a real error. } var err = t.Exception.Flatten(); Log.D(Tag, "ChangeFeedResponseHandler faulted.", err.InnerException ?? err); Error = err.InnerException ?? err; backoff.SleepAppropriateAmountOfTime(); return true; // a real error. }, changesFeedRequestTokenSource.Token, TaskContinuationOptions.OnlyOnFaulted, WorkExecutor.Scheduler); try { var completedTask = TaskEx.WhenAll(successHandler, errorHandler); completedTask.Wait((Int32)ManagerOptions.Default.RequestTimeout.TotalMilliseconds, changesFeedRequestTokenSource.Token); Log.D(Tag, "Finished processing changes feed."); } catch (Exception ex) { // Swallow TaskCancelledExceptions, which will always happen // if either errorHandler or successHandler don't need to fire. if (!(ex.InnerException is OperationCanceledException)) throw ex; } finally { if (changesRequestTask.IsCompleted) { changesRequestTask.Dispose(); } changesRequestTask = null; if (successHandler.IsCompleted) { successHandler.Dispose(); } successHandler = null; if (errorHandler.IsCompleted) { errorHandler.Dispose(); } errorHandler = null; Request.Dispose(); Request = null; changesFeedRequestTokenSource.Dispose(); changesFeedRequestTokenSource = null; } } catch (Exception e) { if (!IsRunning && e.InnerException is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Tag, "Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } finally { if (httpClient != null) { httpClient.Dispose(); } if (mode == ChangeTrackerMode.OneShot) { Stop(); } } } }
public virtual void Run() { running = true; HttpClient httpClient; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Log.TagChangeTracker, "%s: ChangeTracker run() loop aborting because client == null" , this); return; } if (mode == ChangeTracker.ChangeTrackerMode.Continuous) { // there is a failing unit test for this, and from looking at the code the Replication // object will never use Continuous mode anyway. Explicitly prevent its use until // it is demonstrated to actually work. throw new RuntimeException("ChangeTracker does not correctly support continuous mode" ); } httpClient = client.GetHttpClient(); backoff = new ChangeTrackerBackoff(); while (running) { Uri url = GetChangesFeedURL(); if (usePOST) { HttpPost postRequest = new HttpPost(url.ToString()); postRequest.SetHeader("Content-Type", "application/json"); StringEntity entity; try { entity = new StringEntity(ChangesFeedPOSTBody()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } postRequest.SetEntity(entity); request = postRequest; } else { request = new HttpGet(url.ToString()); } AddRequestHeaders(request); // Perform BASIC Authentication if needed bool isUrlBasedUserInfo = false; // If the URL contains user info AND if this a DefaultHttpClient then preemptively set the auth credentials string userInfo = url.GetUserInfo(); if (userInfo != null) { isUrlBasedUserInfo = true; } else { if (authenticator != null) { AuthenticatorImpl auth = (AuthenticatorImpl)authenticator; userInfo = auth.AuthUserInfo(); } } if (userInfo != null) { if (userInfo.Contains(":") && !userInfo.Trim().Equals(":")) { string[] userInfoElements = userInfo.Split(":"); string username = isUrlBasedUserInfo ? URIUtils.Decode(userInfoElements[0]) : userInfoElements [0]; string password = isUrlBasedUserInfo ? URIUtils.Decode(userInfoElements[1]) : userInfoElements [1]; Credentials credentials = new UsernamePasswordCredentials(username, password); if (httpClient is DefaultHttpClient) { DefaultHttpClient dhc = (DefaultHttpClient)httpClient; MessageProcessingHandler preemptiveAuth = new _MessageProcessingHandler_285(credentials ); dhc.AddRequestInterceptor(preemptiveAuth, 0); } } else { Log.W(Log.TagChangeTracker, "RemoteRequest Unable to parse user info, not setting credentials" ); } } try { string maskedRemoteWithoutCredentials = GetChangesFeedURL().ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@" , "://---:---@"); Log.V(Log.TagChangeTracker, "%s: Making request to %s", this, maskedRemoteWithoutCredentials ); HttpResponse response = httpClient.Execute(request); StatusLine status = response.GetStatusLine(); if (status.GetStatusCode() >= 300 && !Utils.IsTransientError(status)) { Log.E(Log.TagChangeTracker, "%s: Change tracker got error %d", this, status.GetStatusCode ()); this.error = new HttpResponseException(status.GetStatusCode(), status.GetReasonPhrase ()); Stop(); } HttpEntity entity = response.GetEntity(); InputStream input = null; if (entity != null) { try { input = entity.GetContent(); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll) { // continuous replications IDictionary <string, object> fullBody = Manager.GetObjectMapper().ReadValue <IDictionary >(input); bool responseOK = ReceivedPollResponse(fullBody); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll && responseOK) { Log.V(Log.TagChangeTracker, "%s: Starting new longpoll", this); backoff.ResetBackoff(); continue; } else { Log.W(Log.TagChangeTracker, "%s: Change tracker calling stop (LongPoll)", this); Stop(); } } else { // one-shot replications JsonFactory jsonFactory = Manager.GetObjectMapper().GetJsonFactory(); JsonParser jp = jsonFactory.CreateJsonParser(input); while (jp.NextToken() != JsonToken.StartArray) { } // ignore these tokens while (jp.NextToken() == JsonToken.StartObject) { IDictionary <string, object> change = (IDictionary)Manager.GetObjectMapper().ReadValue <IDictionary>(jp); if (!ReceivedChange(change)) { Log.W(Log.TagChangeTracker, "Received unparseable change line from server: %s", change ); } } Log.W(Log.TagChangeTracker, "%s: Change tracker calling stop (OneShot)", this); Stop(); break; } backoff.ResetBackoff(); } finally { try { entity.ConsumeContent(); } catch (IOException) { } } } } catch (Exception e) { if (!running && e is IOException) { } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Log.TagChangeTracker, this + ": Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } } Log.V(Log.TagChangeTracker, "%s: Change tracker run loop exiting", this); }
// TODO: Needs to refactored into smaller calls. Each continuation could be its own method, for example. public void Run() { IsRunning = true; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Tag, "ChangeTracker run() loop aborting because client == null"); return; } if (tokenSource.IsCancellationRequested) { tokenSource.Dispose(); tokenSource = new CancellationTokenSource(); } backoff = new ChangeTrackerBackoff(); while (IsRunning && !tokenSource.Token.IsCancellationRequested) { // if (changesRequestTask != null && !changesRequestTask.IsCanceled && !changesRequestTask.IsFaulted) // { // Thread.Sleep(500); // continue; // } var httpClient = client.GetHttpClient(); if (Request != null) { Request.Dispose(); Request = null; } var url = GetChangesFeedURL(); if (UsePost) { Request = new HttpRequestMessage(HttpMethod.Post, url); var body = GetChangesFeedPostBody(); Request.Content = new StringContent(body); Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } else { Request = new HttpRequestMessage(HttpMethod.Get, url); } AddRequestHeaders(Request); var authHeader = AuthUtils.GetAuthenticationHeaderValue(Authenticator, Request.RequestUri); if (authHeader != null) { httpClient.DefaultRequestHeaders.Authorization = authHeader; } var maskedRemoteWithoutCredentials = url.ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(Tag, "Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) break; Task<HttpResponseMessage> changesRequestTask = null; Task<HttpResponseMessage> successHandler; Task<Boolean> errorHandler; try { changesFeedRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); var evt = new ManualResetEvent(false); //successHandler.ConfigureAwait(false).GetAwaiter().OnCompleted(()=>evt.Set()); // ChangeFeedResponseHandler(response); var info = httpClient.SendAsync( Request, HttpCompletionOption.ResponseContentRead, changesFeedRequestTokenSource.Token ); var infoAwaiter = info.ConfigureAwait(false).GetAwaiter(); infoAwaiter.OnCompleted(()=> evt.Set() ); evt.WaitOne(ManagerOptions.Default.RequestTimeout); changesRequestTask = info; //Task.FromResult(info.Result); successHandler = changesRequestTask.ContinueWith<HttpResponseMessage>( ChangeFeedResponseHandler, changesFeedRequestTokenSource.Token, TaskContinuationOptions.LongRunning | TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler ); errorHandler = changesRequestTask.ContinueWith(t => { if (t.IsCanceled) { return false; // Not a real error. } var err = t.Exception.Flatten(); Log.D(Tag, "ChangeFeedResponseHandler faulted.", err.InnerException ?? err); Error = err.InnerException ?? err; return true; // a real error. }, changesFeedRequestTokenSource.Token, TaskContinuationOptions.OnlyOnFaulted, WorkExecutor.Scheduler); try { var completedTask = Task.WhenAll(successHandler, errorHandler); completedTask.Wait((Int32)ManagerOptions.Default.RequestTimeout.TotalMilliseconds, changesFeedRequestTokenSource.Token); Log.D(Tag, "Finished processing changes feed."); } catch (Exception ex) { // Swallow TaskCancelledExceptions, which will always happen // if either errorHandler or successHandler don't need to fire. if (!(ex.InnerException is TaskCanceledException)) throw ex; } finally { changesRequestTask.Dispose(); changesRequestTask = null; successHandler.Dispose(); successHandler = null; errorHandler.Dispose(); errorHandler = null; Request.Dispose(); Request = null; changesFeedRequestTokenSource.Dispose(); if (httpClient != null) { httpClient.Dispose(); } } } catch (Exception e) { if (!IsRunning && e.InnerException is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Tag, "Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } finally { if (mode == ChangeTrackerMode.OneShot) { Stop(); } } // var singleRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); // var cTask = httpClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, singleRequestTokenSource.Token); // var bTask = cTask // .ContinueWith<HttpResponseMessage>(t => // { // if (!IsRunning) // { // return null; // // swallow // } // if (t.IsFaulted && t.Exception.InnerException is IOException) // { // // in this case, just silently absorb the exception because it // // frequently happens when we're shutting down and have to // // close the socket underneath our read. // Log.E(Tag, "Exception in change tracker", t.Exception); // return null; // } // if (!singleRequestTokenSource.IsCancellationRequested && t.Exception != null) // { // var e = t.Exception.InnerException as WebException; // var status = (HttpStatusCode)e.Status; // if ((Int32)status >= 300 && !Misc.IsTransientError(status)) // { // var response = t.Result; // var msg = response.Content != null // ? String.Format("Change tracker got error with status code: {0}", status) // : String.Format("Change tracker got error with status code: {0} and null response content", status); // Log.E(Tag, msg); // Error = new CouchbaseLiteException(msg, new Status(status.GetStatusCode())); // Stop(); // } // backoff.SleepAppropriateAmountOfTime(); // } // return t.Result; // }, singleRequestTokenSource.Token, TaskContinuationOptions.OnlyOnFaulted, WorkExecutor.Scheduler) // .ContinueWith<HttpResponseMessage>(ChangeFeedResponseHandler, singleRequestTokenSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler) // .ContinueWith<HttpResponseMessage>(t => // { // Log.D(Tag, "ChangeFeedResponseHandler finished."); // singleRequestTokenSource.Token.ThrowIfCancellationRequested(); // if (t != null) t.Result.Dispose(); // return null; // }, singleRequestTokenSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler); // changesRequestTask = bTask; // .ContinueWith((t) => // { // Log.D(Tag, "ChangeFeedResponseHandler faulted."); // }, singleRequestTokenSource.Token, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent, TaskScheduler.Default); } }
// TODO: Needs to refactored into smaller calls. Each continuation could be its own method, for example. public void Run() { IsRunning = true; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Tag, "ChangeTracker run() loop aborting because client == null"); return; } if (tokenSource.IsCancellationRequested) { tokenSource.Dispose(); tokenSource = new CancellationTokenSource(); } backoff = new ChangeTrackerBackoff(); while (IsRunning && !tokenSource.Token.IsCancellationRequested) { // if (changesRequestTask != null && !changesRequestTask.IsCanceled && !changesRequestTask.IsFaulted) // { // Thread.Sleep(500); // continue; // } var httpClient = client.GetHttpClient(); if (Request != null) { Request.Dispose(); Request = null; } var url = GetChangesFeedURL(); if (UsePost) { Request = new HttpRequestMessage(HttpMethod.Post, url); var body = GetChangesFeedPostBody(); Request.Content = new StringContent(body); Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } else { Request = new HttpRequestMessage(HttpMethod.Get, url); } AddRequestHeaders(Request); var authHeader = AuthUtils.GetAuthenticationHeaderValue(Authenticator, Request.RequestUri); if (authHeader != null) { httpClient.DefaultRequestHeaders.Authorization = authHeader; } var maskedRemoteWithoutCredentials = url.ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(Tag, "Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) { break; } Task <HttpResponseMessage> changesRequestTask = null; Task <HttpResponseMessage> successHandler; Task <Boolean> errorHandler; try { changesFeedRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); var evt = new ManualResetEvent(false); //successHandler.ConfigureAwait(false).GetAwaiter().OnCompleted(()=>evt.Set()); // ChangeFeedResponseHandler(response); var info = httpClient.SendAsync( Request, HttpCompletionOption.ResponseContentRead, changesFeedRequestTokenSource.Token ); var infoAwaiter = info.ConfigureAwait(false).GetAwaiter(); infoAwaiter.OnCompleted(() => evt.Set() ); evt.WaitOne(ManagerOptions.Default.RequestTimeout); changesRequestTask = info; //Task.FromResult(info.Result); successHandler = changesRequestTask.ContinueWith <HttpResponseMessage>( ChangeFeedResponseHandler, changesFeedRequestTokenSource.Token, TaskContinuationOptions.LongRunning | TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler ); errorHandler = changesRequestTask.ContinueWith(t => { if (t.IsCanceled) { return(false); // Not a real error. } var err = t.Exception.Flatten(); Log.D(Tag, "ChangeFeedResponseHandler faulted.", err.InnerException ?? err); Error = err.InnerException ?? err; return(true); // a real error. }, changesFeedRequestTokenSource.Token, TaskContinuationOptions.OnlyOnFaulted, WorkExecutor.Scheduler); try { var completedTask = Task.WhenAll(successHandler, errorHandler); completedTask.Wait((Int32)ManagerOptions.Default.RequestTimeout.TotalMilliseconds, changesFeedRequestTokenSource.Token); Log.D(Tag, "Finished processing changes feed."); } catch (Exception ex) { // Swallow TaskCancelledExceptions, which will always happen // if either errorHandler or successHandler don't need to fire. if (!(ex.InnerException is TaskCanceledException)) { throw ex; } } finally { changesRequestTask.Dispose(); changesRequestTask = null; successHandler.Dispose(); successHandler = null; errorHandler.Dispose(); errorHandler = null; Request.Dispose(); Request = null; changesFeedRequestTokenSource.Dispose(); if (httpClient != null) { httpClient.Dispose(); } } } catch (Exception e) { if (!IsRunning && e.InnerException is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Tag, "Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } finally { if (mode == ChangeTrackerMode.OneShot) { Stop(); } } // var singleRequestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(tokenSource.Token); // var cTask = httpClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, singleRequestTokenSource.Token); // var bTask = cTask // .ContinueWith<HttpResponseMessage>(t => // { // if (!IsRunning) // { // return null; // // swallow // } // if (t.IsFaulted && t.Exception.InnerException is IOException) // { // // in this case, just silently absorb the exception because it // // frequently happens when we're shutting down and have to // // close the socket underneath our read. // Log.E(Tag, "Exception in change tracker", t.Exception); // return null; // } // if (!singleRequestTokenSource.IsCancellationRequested && t.Exception != null) // { // var e = t.Exception.InnerException as WebException; // var status = (HttpStatusCode)e.Status; // if ((Int32)status >= 300 && !Misc.IsTransientError(status)) // { // var response = t.Result; // var msg = response.Content != null // ? String.Format("Change tracker got error with status code: {0}", status) // : String.Format("Change tracker got error with status code: {0} and null response content", status); // Log.E(Tag, msg); // Error = new CouchbaseLiteException(msg, new Status(status.GetStatusCode())); // Stop(); // } // backoff.SleepAppropriateAmountOfTime(); // } // return t.Result; // }, singleRequestTokenSource.Token, TaskContinuationOptions.OnlyOnFaulted, WorkExecutor.Scheduler) // .ContinueWith<HttpResponseMessage>(ChangeFeedResponseHandler, singleRequestTokenSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler) // .ContinueWith<HttpResponseMessage>(t => // { // Log.D(Tag, "ChangeFeedResponseHandler finished."); // singleRequestTokenSource.Token.ThrowIfCancellationRequested(); // if (t != null) t.Result.Dispose(); // return null; // }, singleRequestTokenSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, WorkExecutor.Scheduler); // changesRequestTask = bTask; // .ContinueWith((t) => // { // Log.D(Tag, "ChangeFeedResponseHandler faulted."); // }, singleRequestTokenSource.Token, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent, TaskScheduler.Default); } }
// TODO: Needs to refactored into smaller calls. Each continuation could be its own method, for example. public void Run() { running = true; HttpClient httpClient; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Tag, this + ": ChangeTracker run() loop aborting because client == null"); return; } if (mode == ChangeTracker.ChangeTrackerMode.Continuous) { // there is a failing unit test for this, and from looking at the code the Replication // object will never use Continuous mode anyway. Explicitly prevent its use until // it is demonstrated to actually work. throw new RuntimeException("ChangeTracker does not correctly support continuous mode"); } httpClient = client.GetHttpClient(); backoff = new ChangeTrackerBackoff(); var shouldBreak = false; while (running) { if (tokenSource.Token.IsCancellationRequested) { break; } var url = GetChangesFeedURL(); Request = new HttpRequestMessage(HttpMethod.Get, url); AddRequestHeaders(Request); // if the URL contains user info AND if this a DefaultHttpClient // then preemptively set/update the auth credentials if (url.UserInfo != null) { Log.V(Tag, "url.getUserInfo(): " + url.GetUserInfo()); var credentials = Request.ToCredentialsFromUri(); if (credentials != null) { var handler = client.HttpHandler; if (handler.Credentials == null || !handler.Credentials.Equals(credentials)) { client.HttpHandler.Credentials = credentials; } } else { Log.W(Tag, this + ": ChangeTracker Unable to parse user info, not setting credentials"); } } try { var requestStatus = CurrentRequest == null ? TaskStatus.Canceled : CurrentRequest.Status; Log.V(Tag, this + ": Current Request Status: " + requestStatus); if (requestStatus == TaskStatus.Running || requestStatus == TaskStatus.WaitingForActivation) { //System.Threading.Thread.Sleep(5000); continue; } var maskedRemoteWithoutCredentials = GetChangesFeedURL().ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(Tag, this + ": Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) { break; } CurrentRequest = httpClient.SendAsync(Request) .ContinueWith <HttpResponseMessage>(request => { if (request.Status != System.Threading.Tasks.TaskStatus.RanToCompletion && request.IsFaulted) { Log.E(Tag, this + ": Change tracker got error " + Extensions.ToString(request.Status)); throw request.Exception; } return(request.Result); }, this.tokenSource.Token) .ContinueWith <Task <Byte[]> >((request) => { var status = request.Result.StatusCode; if ((Int32)status >= 300) { var msg = String.Format("Change tracker got error: {0}", status); Log.E(Tag, msg); Error = new CouchbaseLiteException(msg, new Status(status.GetStatusCode())); Stop(); } return(request.Result.Content.ReadAsByteArrayAsync()); }, this.tokenSource.Token) .ContinueWith((Task <Task <Byte[]> > response) => { if (response.Status != System.Threading.Tasks.TaskStatus.RanToCompletion && !response.IsFaulted && response.Result != null) { return; } var result = response.Result.Result; if (mode == ChangeTrackerMode.LongPoll) { var fullBody = Manager.GetObjectMapper().ReadValue <IDictionary <string, object> >(result.AsEnumerable()); var responseOK = ReceivedPollResponse(fullBody); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll && responseOK) { Log.V(Tag, this + ": Starting new longpoll"); backoff.ResetBackoff(); return; } else { Log.W(Tag, this + ": Change tracker calling stop"); Stop(); } } else { var results = Manager.GetObjectMapper().ReadValue <IDictionary <String, Object> >(result.AsEnumerable()); var resultsValue = results["results"] as Newtonsoft.Json.Linq.JArray; foreach (var item in resultsValue) { IDictionary <String, Object> change = null; try { change = item.ToObject <IDictionary <String, Object> >(); } catch (Exception) { Log.E(Tag, this + string.Format(": Received unparseable change line from server: {0}", change)); } if (!ReceivedChange(change)) { Log.W(Tag, this + string.Format(": Received unparseable change line from server: {0}", change)); } } Stop(); shouldBreak = true; return; } backoff.ResetBackoff(); }, tokenSource.Token); } catch (Exception e) { if (!running && e is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Tag, this + ": Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } if (shouldBreak) { break; } } if (!tokenSource.Token.IsCancellationRequested) { // Handle cancellation requests while we are waiting. // e.g. when Stop() is called from another thread. try { CurrentRequest.Wait(tokenSource.Token); } catch (Exception) { Log.V(Tag, this + ": Run loop was cancelled."); } } Log.V(Tag, this + ": Change tracker run loop exiting"); }
// TODO: Needs to refactored into smaller calls. Each continuation could be its own method, for example. public void Run() { running = true; HttpClient httpClient; if (client == null) { // This is a race condition that can be reproduced by calling cbpuller.start() and cbpuller.stop() // directly afterwards. What happens is that by the time the Changetracker thread fires up, // the cbpuller has already set this.client to null. See issue #109 Log.W(Tag, this + ": ChangeTracker run() loop aborting because client == null"); return; } if (mode == ChangeTracker.ChangeTrackerMode.Continuous) { // there is a failing unit test for this, and from looking at the code the Replication // object will never use Continuous mode anyway. Explicitly prevent its use until // it is demonstrated to actually work. throw new RuntimeException("ChangeTracker does not correctly support continuous mode"); } httpClient = client.GetHttpClient(); backoff = new ChangeTrackerBackoff(); var shouldBreak = false; while (running) { if (tokenSource.Token.IsCancellationRequested) break; var url = GetChangesFeedURL(); Request = new HttpRequestMessage(HttpMethod.Get, url); AddRequestHeaders(Request); // if the URL contains user info AND if this a DefaultHttpClient // then preemptively set/update the auth credentials if (url.UserInfo != null) { Log.V(Tag, "url.getUserInfo(): " + url.GetUserInfo()); var credentials = Request.ToCredentialsFromUri(); if (credentials != null) { var handler = client.HttpHandler; if (handler.Credentials == null || !handler.Credentials.Equals(credentials)) client.HttpHandler.Credentials = credentials; } else { Log.W(Tag, this + ": ChangeTracker Unable to parse user info, not setting credentials"); } } try { var requestStatus = CurrentRequest == null ? TaskStatus.Canceled : CurrentRequest.Status; Log.V(Tag, this + ": Current Request Status: " + requestStatus); if (requestStatus == TaskStatus.Running || requestStatus == TaskStatus.WaitingForActivation) { //System.Threading.Thread.Sleep(5000); continue; } var maskedRemoteWithoutCredentials = GetChangesFeedURL().ToString(); maskedRemoteWithoutCredentials = maskedRemoteWithoutCredentials.ReplaceAll("://.*:.*@", "://---:---@"); Log.V(Tag, this + ": Making request to " + maskedRemoteWithoutCredentials); if (tokenSource.Token.IsCancellationRequested) break; CurrentRequest = httpClient.SendAsync(Request) .ContinueWith<HttpResponseMessage>(request=> { if (request.Status != System.Threading.Tasks.TaskStatus.RanToCompletion && request.IsFaulted) { Log.E(Tag, this + ": Change tracker got error " + Extensions.ToString(request.Status)); throw request.Exception; } return request.Result; }, this.tokenSource.Token) .ContinueWith<Task<Byte[]>>((request) => { var status = request.Result.StatusCode; if ((Int32)status >= 300) { var msg = String.Format("Change tracker got error: {0}", status); Log.E(Tag, msg); Error = new CouchbaseLiteException (msg, new Status (status.GetStatusCode ())); Stop(); } return request.Result.Content.ReadAsByteArrayAsync(); }, this.tokenSource.Token) .ContinueWith((Task<Task<Byte[]>> response) => { if (response.Status != System.Threading.Tasks.TaskStatus.RanToCompletion && !response.IsFaulted && response.Result != null) return; var result = response.Result.Result; if (mode == ChangeTrackerMode.LongPoll) { var fullBody = Manager.GetObjectMapper().ReadValue<IDictionary<string, object>>(result.AsEnumerable()); var responseOK = ReceivedPollResponse(fullBody); if (mode == ChangeTracker.ChangeTrackerMode.LongPoll && responseOK) { Log.V(Tag, this + ": Starting new longpoll"); backoff.ResetBackoff(); return; } else { Log.W(Tag, this + ": Change tracker calling stop"); Stop(); } } else { var results = Manager.GetObjectMapper().ReadValue<IDictionary<String, Object>>(result.AsEnumerable()); var resultsValue = results["results"] as Newtonsoft.Json.Linq.JArray; foreach (var item in resultsValue) { IDictionary<String, Object> change = null; try { change = item.ToObject<IDictionary<String, Object>>(); } catch (Exception) { Log.E(Tag, this + string.Format(": Received unparseable change line from server: {0}", change)); } if (!ReceivedChange(change)) { Log.W(Tag, this + string.Format(": Received unparseable change line from server: {0}", change)); } } Stop(); shouldBreak = true; return; } backoff.ResetBackoff(); }, tokenSource.Token); } catch (Exception e) { if (!running && e is IOException) { // swallow } else { // in this case, just silently absorb the exception because it // frequently happens when we're shutting down and have to // close the socket underneath our read. Log.E(Tag, this + ": Exception in change tracker", e); } backoff.SleepAppropriateAmountOfTime(); } if (shouldBreak) break; } if (!tokenSource.Token.IsCancellationRequested) { // Handle cancellation requests while we are waiting. // e.g. when Stop() is called from another thread. try { CurrentRequest.Wait (tokenSource.Token); } catch (Exception) { Log.V(Tag, this + ": Run loop was cancelled."); } } Log.V(Tag, this + ": Change tracker run loop exiting"); }