static IEtcdWatchStatus MonitorEtcd(IEtcdWatchStatus status) { IEtcdClient client = new EtcdClient(); status = client.Watch(status, null, (a, b) => { Console.WriteLine("{0}-{1}", b.Node.Key, b.Node.Value); return true; }, (a,b) => { if (b.PrevErrorsCount%5 == 0){ Console.WriteLine("Another 5 errors:" + b.Exception.Message); } if (a.AllFailed && a.Clients.Select(c => c.LastError.PrevErrorsCount).All(num => num > 15)) { Console.WriteLine("Fatal Error in etcd cluster, Reinitializing..."); a.AbortAll(); //implicit not running MonitorEtcd(status); } return true; },true); return status; }
/// <summary> /// Sets up a watch on a keyspace and will call the callback when triggered /// </summary> /// <param name="externalStatus">previous status to update</param> /// <param name="key">key</param> /// <param name="followUp">callback</param> /// <param name="onError">optional error handler</param> /// <param name="recursive">watch subkeys?</param> /// <param name="timeout">How long will we watch?</param> /// <param name="waitIndex">Index to wait from</param> public IEtcdWatchStatus Watch(IEtcdWatchStatus externalStatus, string key, Func<IEtcdWatchStatus, EtcdResponse, bool> followUp, Func<IEtcdWatchStatus, EtcdErrorResult, bool> onError = null, bool recursive = false, int? timeout = null, int? waitIndex = null) { if (followUp == null) throw new ArgumentNullException("followUp"); //if (key == null) // throw new ArgumentNullException("key"); if(externalStatus == null ) externalStatus = new EtcdWatchStatus(); else if(!(externalStatus is EtcdWatchStatus)) throw new ArgumentException("you cant implement externalStatus yourself, but should obtain it from previous call to Watch"); EtcdWatchStatus status = (EtcdWatchStatus)externalStatus; status.Cleanup(); //handle for forced close, if such //default error handler if (onError == null) { onError = (a,b) => { //rest a while Thread.Sleep(50); return true; }; } Action<IRestResponse<EtcdResponse>, RestRequestAsyncHandle> callback = null; callback = (b,c) => { if (!status.Running) return; var baseUrl = getResponseBaseUri(b.Request, c); //some weird fix for rest response if (String.IsNullOrWhiteSpace(b.Server)) b.Server = baseUrl.ToString(); var clientContext = status.Clients.First(a => a.Server == baseUrl); if (checkForError(b)) { clientContext.State = ClientState.Failed; if(clientContext.LastError==null) clientContext.LastError = new EtcdErrorResult(); //check for client shutdown or general watch abort if (status.LockedCheck(()=>onError(status,clientContext.LastError.Update(constructException(b)))) && status.Running) { //we are still alive, try reconnect clientContext.CurrentRequest = _clients .First(a => a.BaseUrl == baseUrl) .GetAsync<EtcdResponse>(prepareWaitOnKeyRequest(baseUrl, key, recursive, timeout, waitIndex), (r, h) => callback(r, h)); } else { clientContext.State = ClientState.Down; } return; } clientContext.State = ClientState.Succeeded; //avoid duplicated results from cluster and try to stop gracefully, if required if (!status.Running) //check we are already aborted return; lock (status) { if (!status.Running) return; if (!status.LastResults.ContainsKey(b.Data.Node.Key) || status.LastResults[b.Data.Node.Key] != b.Data.Node.ModifiedIndex) { status.LastResults[b.Data.Node.Key] = b.Data.Node.ModifiedIndex; if (!followUp(status, processRestResponse(b))) { status.Running = false; status.AbortAll(); return; } } } //now wait for changes... //see SetWaitable call clientContext.CurrentRequest = _clients.First(a => a.BaseUrl == clientContext.Server) .GetAsync<EtcdResponse>(b.Request.SetWaitable(), (r, h) => callback(r, h)); }; //all first requests are should return current value immediately _clients.ForEach(a => { ClientWatchContext context; status.Clients.Add(context = new ClientWatchContext(a.BaseUrl) {State = ClientState.InProgress}); context.CurrentRequest = a.GetAsync<EtcdResponse>( prepareWaitOnKeyRequest(a.BaseUrl,key, recursive, timeout, waitIndex).SetWaitable(),(r, h) => callback(r, h)); }); return status; }