Add event queue and HTTP retry
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
index 2dc7ed2..c640b7e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -1,13 +1,11 @@
 package com.googlesource.gerrit.plugins.chatgpt;
 
-import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
 import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
 import lombok.extern.slf4j.Slf4j;
 
-import java.io.IOException;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -25,7 +23,7 @@
         this.openAiClient = openAiClient;
     }
 
-    public void review(Configuration config, String fullChangeId) throws IOException, InterruptedException, NoSuchProjectException {
+    public void review(Configuration config, String fullChangeId) throws Exception {
         String patchSet = gerritClient.getPatchSet(config, fullChangeId);
         if (config.isPatchSetReduction()) {
             patchSet = reducePatchSet(patchSet);
@@ -72,8 +70,7 @@
                 .collect(Collectors.joining("\n"));
     }
 
-    private String getReviewSuggestion(Configuration config, String changeId, String patchSet)
-            throws IOException, InterruptedException {
+    private String getReviewSuggestion(Configuration config, String changeId, String patchSet) throws Exception {
         List<String> patchLines = Arrays.asList(patchSet.split("\n"));
         if (patchLines.size() > config.getMaxReviewLines()) {
             log.warn("Patch set too large. Skipping review. changeId: {}", changeId);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
index df0cecf..c5d1430 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
@@ -9,11 +9,9 @@
 
 import java.io.IOException;
 import java.net.URI;
-import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.nio.charset.StandardCharsets;
-import java.time.Duration;
 import java.util.Base64;
 import java.util.HashMap;
 import java.util.Map;
@@ -23,14 +21,10 @@
 @Slf4j
 @Singleton
 public class GerritClient {
-
     private final Gson gson = new Gson();
+    private final HttpClientWithRetry httpClientWithRetry = new HttpClientWithRetry();
 
-    private final HttpClient httpClient = HttpClient.newBuilder()
-            .connectTimeout(Duration.ofMinutes(5))
-            .build();
-
-    public String getPatchSet(Configuration config, String fullChangeId) throws IOException, InterruptedException {
+    public String getPatchSet(Configuration config, String fullChangeId) throws Exception {
         HttpRequest request = HttpRequest.newBuilder()
                 .header(HttpHeaders.AUTHORIZATION, generateBasicAuth(config.getGerritUserName(),
                         config.getGerritPassword()))
@@ -38,7 +32,8 @@
                         + UriResourceLocator.gerritPatchSetUri(fullChangeId)))
                 .build();
 
-        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+        //HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+        HttpResponse<String> response = httpClientWithRetry.execute(request);
 
         if (response.statusCode() != HTTP_OK) {
             log.error("Failed to get patch. Response: {}", response);
@@ -55,7 +50,7 @@
         return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
     }
 
-    public void postComment(Configuration config, String fullChangeId, String message) throws IOException, InterruptedException {
+    public void postComment(Configuration config, String fullChangeId, String message) throws Exception {
         Map<String, String> map = new HashMap<>();
         map.put("message", message);
         String json = gson.toJson(map);
@@ -69,7 +64,8 @@
                 .POST(HttpRequest.BodyPublishers.ofString(json))
                 .build();
 
-        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+        //HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+        HttpResponse<String> response = httpClientWithRetry.execute(request);
 
         if (response.statusCode() != HTTP_OK) {
             log.error("Review post failed with status code: {}", response.statusCode());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/HttpClientWithRetry.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/HttpClientWithRetry.java
new file mode 100644
index 0000000..5e9ec56
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/HttpClientWithRetry.java
@@ -0,0 +1,37 @@
+package com.googlesource.gerrit.plugins.chatgpt.client;
+
+import com.github.rholder.retry.*;
+import com.google.inject.Singleton;
+
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import static java.net.HttpURLConnection.HTTP_OK;
+
+@Singleton
+public class HttpClientWithRetry {
+    private final Retryer<HttpResponse<String>> retryer;
+
+    private final HttpClient httpClient = HttpClient.newBuilder()
+            .connectTimeout(Duration.ofMinutes(5))
+            .build();
+
+    public HttpClientWithRetry() {
+        this.retryer = RetryerBuilder.<HttpResponse<String>>newBuilder()
+                .retryIfException()
+                .retryIfResult(response -> response.statusCode() != HTTP_OK)
+                .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
+                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
+                .build();
+    }
+
+    public HttpResponse<String> execute(HttpRequest request) throws ExecutionException, RetryException {
+        return retryer.call(() -> httpClient.send(request, HttpResponse.BodyHandlers.ofString()));
+    }
+
+}
+
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
index a591ba4..f595d89 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
@@ -11,31 +11,22 @@
 import java.io.IOException;
 import java.io.StringReader;
 import java.net.URI;
-import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
-import java.time.Duration;
 import java.util.List;
 import java.util.Optional;
 
-import static java.net.HttpURLConnection.HTTP_OK;
-
 @Slf4j
 @Singleton
 public class OpenAiClient {
     private final Gson gson = new Gson();
+    private final HttpClientWithRetry httpClientWithRetry = new HttpClientWithRetry();
 
-    private final HttpClient httpClient = HttpClient.newBuilder()
-            .connectTimeout(Duration.ofMinutes(5))
-            .build();
-
-    public String ask(Configuration config, String patchSet) throws IOException, InterruptedException {
+    public String ask(Configuration config, String patchSet) throws Exception {
         HttpRequest request = createRequest(config, patchSet);
 
-        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
-        if (response.statusCode() != HTTP_OK) {
-            throw new IOException("Unexpected response " + response);
-        }
+        HttpResponse<String> response = httpClientWithRetry.execute(request);
+
         String body = response.body();
         if (body == null) {
             throw new IOException("responseBody is null");
@@ -53,13 +44,13 @@
     }
 
     private HttpRequest createRequest(Configuration config, String patchSet) {
-        String jsonRequest = createRequestBody(config, patchSet);
+        String requestBody = createRequestBody(config, patchSet);
 
         return HttpRequest.newBuilder()
                 .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getGptToken())
                 .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
                 .uri(URI.create(URI.create(config.getGptDomain()) + UriResourceLocator.chatCompletionsUri()))
-                .POST(HttpRequest.BodyPublishers.ofString(jsonRequest))
+                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                 .build();
     }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
index 1b2c204..c6a1d63 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
@@ -11,8 +11,6 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import java.io.IOException;
-
 import static junit.framework.TestCase.assertNotNull;
 import static org.mockito.Mockito.when;
 
@@ -31,7 +29,7 @@
     private OpenAiClient openAiClient;
 
     @Test
-    public void sayHelloToGPT() throws IOException, InterruptedException {
+    public void sayHelloToGPT() throws Exception {
         when(config.getGptDomain()).thenReturn(Configuration.OPENAI_DOMAIN);
         when(config.getGptToken()).thenReturn("Your GPT token");
         when(config.getGptModel()).thenReturn(Configuration.DEFAULT_GPT_MODEL);
@@ -43,7 +41,7 @@
     }
 
     @Test
-    public void getPatchSet() throws IOException, InterruptedException {
+    public void getPatchSet() throws Exception {
         when(config.getGerritAuthBaseUrl()).thenReturn("Your Gerrit URL");
         when(config.getGerritUserName()).thenReturn("Your Gerrit username");
         when(config.getGerritPassword()).thenReturn("Your Gerrit password");
@@ -54,7 +52,7 @@
     }
 
     @Test
-    public void postComment() throws IOException, InterruptedException {
+    public void postComment() throws Exception {
         when(config.getGerritAuthBaseUrl()).thenReturn("Your Gerrit URL");
         when(config.getGerritUserName()).thenReturn("Your Gerrit username");
         when(config.getGerritPassword()).thenReturn("Your Gerrit password");