private async Task <bool> ExecuteAsync() { string joinCharacter = ApiEndpoint.Contains("?") ? "&" : "?"; string apiUrl = ApiEndpoint + joinCharacter + "access_token=" + Uri.EscapeDataString(AccessToken); Log.LogMessage(MessageImportance.Low, "Posting job to {0}", ApiEndpoint); Log.LogMessage(MessageImportance.Low, "Event json is ", EventDataPath); using (HttpClient client = new HttpClient()) { int retryCount = 15; while (true) { HttpResponseMessage response; using (Stream stream = File.OpenRead(EventDataPath)) { HttpContent contentStream = new StreamContent(stream); contentStream.Headers.Add("Content-Type", "application/json"); response = await client.PostAsync(apiUrl, contentStream); } if (response.IsSuccessStatusCode) { JObject responseObject; using (Stream stream = await response.Content.ReadAsStreamAsync()) using (StreamReader streamReader = new StreamReader(stream)) using (JsonReader jsonReader = new JsonTextReader(streamReader)) { responseObject = JObject.Load(jsonReader); } JobId = (string)responseObject["Name"]; if (String.IsNullOrEmpty(JobId)) { Log.LogError("Publish to '{0}' did not return a job ID", ApiEndpoint); return(false); } Log.LogMessage(MessageImportance.High, "Started Helix job: CorrelationId = {0}", JobId); return(true); } if (retryCount-- <= 0) { Log.LogError( "Unable to publish to '{0}' after 15 retries. Received status code: {1} {2}", ApiEndpoint, response.StatusCode, response.ReasonPhrase); return(false); } Log.LogWarning("Failed to publish to '{0}', {1} retries remaining", ApiEndpoint, retryCount); } } }
private async Task <bool> ExecuteAsync() { List <ITaskItem> jobIds = new List <ITaskItem>(); string joinCharacter = ApiEndpoint.Contains("?") ? "&" : "?"; string apiUrl = ApiEndpoint + joinCharacter + "access_token=" + Uri.EscapeDataString(AccessToken); Log.LogMessage(MessageImportance.Normal, "Posting job to {0}", ApiEndpoint); Log.LogMessage(MessageImportance.Low, "Using Job Event json from ", EventDataPath); string buildJsonText = File.ReadAllText(EventDataPath); List <JObject> allBuilds = new List <JObject>(); try { allBuilds.AddRange(JsonConvert.DeserializeObject <List <JObject> >(buildJsonText)); } catch (JsonSerializationException) { // If this fails, we'll let it tear us down. // Since if this isn't even valid JSON there's no use posting it. allBuilds.Add(JsonConvert.DeserializeObject <JObject>(buildJsonText)); } using (HttpClient client = new HttpClient()) { const int MaxAttempts = 15; // add a bit of randomness to the retry delay var rng = new Random(); int retryCount = MaxAttempts; foreach (JObject jobStartMessage in allBuilds) { string queueId = (string)jobStartMessage["QueueId"]; // This should never happen. if (string.IsNullOrEmpty(queueId)) { Log.LogError("Helix Job Start messages must have a value for 'QueueId' "); } bool keepTrying = true; while (keepTrying) { HttpResponseMessage response = new HttpResponseMessage(); try { // This tortured way to get the HTTPContent is to work around that StringContent doesn't allow application/json HttpContent contentStream = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes(jobStartMessage.ToString()))); contentStream.Headers.Add("Content-Type", "application/json"); response = await client.PostAsync(apiUrl, contentStream); if (response.IsSuccessStatusCode) { JObject responseObject = new JObject(); using (Stream stream = await response.Content.ReadAsStreamAsync()) using (StreamReader streamReader = new StreamReader(stream)) { string jsonResponse = streamReader.ReadToEnd(); try { using (JsonReader jsonReader = new JsonTextReader(new StringReader(jsonResponse))) { responseObject = JObject.Load(jsonReader); } } catch { Log.LogWarning($"Hit exception attempting to parse JSON response. Raw response string: {Environment.NewLine} {jsonResponse}"); } } string jobId = (string)responseObject["Name"]; if (String.IsNullOrEmpty(jobId)) { Log.LogError("Publish to '{0}' did not return a job ID", ApiEndpoint); } TaskItem helixJobStartedInfo = new TaskItem(jobId); helixJobStartedInfo.SetMetadata("CorrelationId", jobId); helixJobStartedInfo.SetMetadata("QueueId", queueId); helixJobStartedInfo.SetMetadata("QueueTimeUtc", DateTime.UtcNow.ToString()); jobIds.Add(helixJobStartedInfo); Log.LogMessage(MessageImportance.High, "Started Helix job: CorrelationId = {0}", jobId); keepTrying = false; } else { string responseContent = await response.Content.ReadAsStringAsync(); Log.LogWarning($"Helix Api Response: StatusCode {response.StatusCode} {responseContent}"); } } // still allow other types of exceptions to tear down the task for now catch (HttpRequestException toLog) { Log.LogWarning("Exception thrown attempting to submit job to Helix:"); Log.LogWarningFromException(toLog, true); } if (retryCount-- <= 0) { Log.LogError($"Unable to publish to '{ApiEndpoint}' after {MaxAttempts} retries. Received status code: {response.StatusCode} {response.ReasonPhrase}"); keepTrying = false; } if (keepTrying) { Log.LogWarning("Failed to publish to '{0}', {1} retries remaining", ApiEndpoint, retryCount); int delay = (MaxAttempts - retryCount) * rng.Next(1, 7); await System.Threading.Tasks.Task.Delay(delay * 1000); } } } JobIds = jobIds.ToArray(); // Number of queued builds = number in that file == success. return(allBuilds.Count == jobIds.Count); } }
private async Task <bool> ExecuteAsync() { string joinCharacter = ApiEndpoint.Contains("?") ? "&" : "?"; string apiUrl = ApiEndpoint + joinCharacter + "access_token=" + Uri.EscapeDataString(AccessToken); Log.LogMessage(MessageImportance.Low, "Posting job to {0}", ApiEndpoint); Log.LogMessage(MessageImportance.Low, "Event json is ", EventDataPath); using (HttpClient client = new HttpClient()) { const int MaxAttempts = 15; // add a bit of randomness to the retry delay var rng = new Random(); int retryCount = MaxAttempts; while (true) { HttpResponseMessage response = new HttpResponseMessage(); try { using (Stream stream = File.OpenRead(EventDataPath)) { HttpContent contentStream = new StreamContent(stream); contentStream.Headers.Add("Content-Type", "application/json"); response = await client.PostAsync(apiUrl, contentStream); } if (response.IsSuccessStatusCode) { JObject responseObject; using (Stream stream = await response.Content.ReadAsStreamAsync()) using (StreamReader streamReader = new StreamReader(stream)) using (JsonReader jsonReader = new JsonTextReader(streamReader)) { responseObject = JObject.Load(jsonReader); } JobId = (string)responseObject["Name"]; if (String.IsNullOrEmpty(JobId)) { Log.LogError("Publish to '{0}' did not return a job ID", ApiEndpoint); return(false); } Log.LogMessage(MessageImportance.High, "Started Helix job: CorrelationId = {0}", JobId); return(true); } else { string responseContent = await response.Content.ReadAsStringAsync(); Log.LogWarning($"Helix Api Response: StatusCode {response.StatusCode} {responseContent}"); } } // still allow other types of exceptions to tear down the task for now catch (HttpRequestException toLog) { Log.LogWarning("Exception thrown attempting to submit job to Helix:"); Log.LogWarningFromException(toLog, true); } if (retryCount-- <= 0) { Log.LogError($"Unable to publish to '{ApiEndpoint}' after {MaxAttempts} retries. Received status code: {response.StatusCode} {response.ReasonPhrase}"); return(false); } Log.LogWarning("Failed to publish to '{0}', {1} retries remaining", ApiEndpoint, retryCount); int delay = (MaxAttempts - retryCount) * rng.Next(1, 7); await System.Threading.Tasks.Task.Delay(delay * 1000); } } }
private async Task <bool> ExecuteAsync() { List <ITaskItem> jobIds = new List <ITaskItem>(); string apiUrl = ApiEndpoint; if (!String.IsNullOrEmpty(AccessToken)) { string joinCharacter = ApiEndpoint.Contains("?") ? "&" : "?"; apiUrl = ApiEndpoint + joinCharacter + "access_token=" + Uri.EscapeDataString(AccessToken); } Log.LogMessage(MessageImportance.Normal, "Posting job to {0}", ApiEndpoint); Log.LogMessage(MessageImportance.Low, "Using Job Event json from ", EventDataPath); string buildJsonText = File.ReadAllText(EventDataPath); List <JObject> allBuilds = new List <JObject>(); try { allBuilds.AddRange(JsonConvert.DeserializeObject <List <JObject> >(buildJsonText)); } catch (JsonSerializationException) { // If this fails, we'll let it tear us down. // Since if this isn't even valid JSON there's no use posting it. allBuilds.Add(JsonConvert.DeserializeObject <JObject>(buildJsonText)); } using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(30) // Default is 100 seconds. 15 timeouts @ 30 seconds = ~7:30 }) { const int MaxAttempts = 15; // add a bit of randomness to the retry delay var rng = new Random(); // We'll use this to be sure TaskCancelledException comes from timeouts CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); foreach (JObject jobStartMessage in allBuilds) { int retryCount = MaxAttempts; bool keepTrying = true; string queueId = (string)jobStartMessage["QueueId"]; if (string.IsNullOrEmpty(queueId)) { Log.LogError("Helix Job Start messages must have a value for 'QueueId' "); keepTrying = false; // this will fail in the API, so we won't even try. } // Provides a way for the API to realize that a given job has been recently queued // which allows us to retry in the case of ambiguous results such as HttpClient timeout. string jobStartIdentifier = Guid.NewGuid().ToString("N"); jobStartMessage["JobStartIdentifier"] = jobStartIdentifier; Log.LogMessage(MessageImportance.Low, $"Sending job start with identifier '{jobStartIdentifier}'"); while (keepTrying) { HttpResponseMessage response = new HttpResponseMessage(); try { // This workaround to get the HTTPContent is to work around that StringContent doesn't allow application/json HttpContent contentStream = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes(jobStartMessage.ToString()))); contentStream.Headers.Add("Content-Type", "application/json"); response = await client.PostAsync(apiUrl, contentStream, cancelTokenSource.Token); if (response.IsSuccessStatusCode) { JObject responseObject = new JObject(); using (Stream stream = await response.Content.ReadAsStreamAsync()) using (StreamReader streamReader = new StreamReader(stream)) { string jsonResponse = streamReader.ReadToEnd(); try { using (JsonReader jsonReader = new JsonTextReader(new StringReader(jsonResponse))) { responseObject = JObject.Load(jsonReader); } } catch { Log.LogWarning($"Hit exception attempting to parse JSON response. Raw response string: {Environment.NewLine} {jsonResponse}"); } } string jobId = (string)responseObject["Name"]; if (String.IsNullOrEmpty(jobId)) { Log.LogError("Publish to '{0}' did not return a job ID", ApiEndpoint); } TaskItem helixJobStartedInfo = new TaskItem(jobId); helixJobStartedInfo.SetMetadata("CorrelationId", jobId); helixJobStartedInfo.SetMetadata("QueueId", queueId); helixJobStartedInfo.SetMetadata("QueueTimeUtc", DateTime.UtcNow.ToString()); jobIds.Add(helixJobStartedInfo); Log.LogMessage(MessageImportance.High, "Started Helix job: CorrelationId = {0}", jobId); keepTrying = false; } else { string responseContent = await response.Content.ReadAsStringAsync(); Log.LogWarning($"Helix Api Response: StatusCode {response.StatusCode} {responseContent}"); } } // still allow other types of exceptions to tear down the task for now catch (HttpRequestException toLog) { Log.LogWarning("Exception thrown attempting to submit job to Helix:"); Log.LogWarningFromException(toLog, true); } catch (TaskCanceledException possibleClientTimeout) { if (possibleClientTimeout.CancellationToken != cancelTokenSource.Token) { // This is a timeout. Since we provided a JobIdentifier value, we can retry. Log.LogWarning($"HttpClient timeout while attempting to POST new job to '{queueId}', will retry. Job Start Identifier: {jobStartIdentifier}"); } else { // Something else caused cancel, throw it. Should not ever get here. throw; } } if (retryCount-- <= 0) { Log.LogError($"Unable to publish to '{ApiEndpoint}' after {MaxAttempts} retries. Received status code: {response.StatusCode} {response.ReasonPhrase}"); keepTrying = false; } if (keepTrying) { Log.LogWarning("Failed to publish to '{0}', {1} retries remaining", ApiEndpoint, retryCount); int delay = (MaxAttempts - retryCount) * rng.Next(1, 7); await System.Threading.Tasks.Task.Delay(delay * 1000); } } } JobIds = jobIds.ToArray(); // Number of queued builds = number in that file == success. // If any timeouts or other failures occur this will cause the task to fail. return(allBuilds.Count == jobIds.Count); } }