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"); }
// 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() { 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 (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 = 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 { 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(); } } } }