Enable concurrent commit processing
Resolved issues associated with processing concurrent commits, thereby
allowing for their concurrent handling.
Jira-Id: IT-103
Change-Id: I749bb5b9e02ff66ef3879e26ec6772282d479e78
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
index 15cf7b7..c06abd1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
@@ -2,7 +2,6 @@
import com.google.gerrit.server.events.EventListener;
import com.google.inject.AbstractModule;
-import com.google.inject.Singleton;
import com.google.inject.multibindings.Multibinder;
import com.googlesource.gerrit.plugins.chatgpt.listener.GerritListener;
@@ -11,6 +10,6 @@
@Override
protected void configure() {
Multibinder<EventListener> eventListenerBinder = Multibinder.newSetBinder(binder(), EventListener.class);
- eventListenerBinder.addBinding().to(GerritListener.class).in(Singleton.class);
+ eventListenerBinder.addBinding().to(GerritListener.class);
}
}
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 1b624eb..d69b1ab 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -4,12 +4,8 @@
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
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.InlineCode;
-import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
+import com.googlesource.gerrit.plugins.chatgpt.client.*;
import com.googlesource.gerrit.plugins.chatgpt.client.model.ChatGptSuggestionPoint;
-import com.googlesource.gerrit.plugins.chatgpt.client.FileDiffProcessed;
import com.googlesource.gerrit.plugins.chatgpt.client.model.GerritCodeRange;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
import lombok.Setter;
@@ -18,10 +14,9 @@
import java.lang.reflect.Type;
import java.util.*;
-import static com.googlesource.gerrit.plugins.chatgpt.client.ReviewUtils.extractID;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.ReviewUtils.extractID;
@Slf4j
-@Singleton
public class PatchSetReviewer {
private static final String SPLIT_REVIEW_MSG = "Too many changes. Please consider splitting into patches smaller " +
"than %s lines for review.";
@@ -44,7 +39,7 @@
public void review(Configuration config, String fullChangeId) throws Exception {
reviewBatches = new ArrayList<>();
- commentProperties = gerritClient.getCommentProperties();
+ commentProperties = gerritClient.getCommentProperties(fullChangeId);
String patchSet = gerritClient.getPatchSet(fullChangeId, isCommentEvent);
if (patchSet.isEmpty()) {
log.info("No file to review has been found in the PatchSet");
@@ -55,7 +50,7 @@
String reviewSuggestion = getReviewSuggestion(config, fullChangeId, patchSet);
log.debug("ChatGPT response: {}", reviewSuggestion);
if (isCommentEvent || config.getGptReviewByPoints()) {
- retrieveReviewFromJson(reviewSuggestion);
+ retrieveReviewFromJson(reviewSuggestion, fullChangeId);
}
else {
splitReviewIntoBatches(reviewSuggestion);
@@ -117,11 +112,11 @@
return gerritCommentRange;
}
- private void retrieveReviewFromJson(String review) {
+ private void retrieveReviewFromJson(String review, String fullChangeId) {
review = review.replaceAll("^`*(?:json)?\\s*|\\s*`+$", "");
Type chatGptResponseListType = new TypeToken<List<ChatGptSuggestionPoint>>(){}.getType();
List<ChatGptSuggestionPoint> reviewJson = gson.fromJson(review, chatGptResponseListType);
- fileDiffsProcessed = gerritClient.getFileDiffsProcessed();
+ fileDiffsProcessed = gerritClient.getFileDiffsProcessed(fullChangeId);
for (ChatGptSuggestionPoint suggestion : reviewJson) {
HashMap<String, Object> batchMap = new HashMap<>();
if (suggestion.getId() != null) {
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 61291af..6c44c4c 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
@@ -2,44 +2,30 @@
import com.google.gerrit.server.events.Event;
import com.google.gson.JsonObject;
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.utils.SingletonManager;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
-class GerritClientPatchSetInjector extends AbstractModule {
- @Override
- protected void configure() {
- bind(GerritClientPatchSet.class).in(Singleton.class);
- }
-}
-
-class GerritClientCommentsInjector extends AbstractModule {
- @Override
- protected void configure() {
- bind(GerritClientComments.class).in(Singleton.class);
- }
-}
-
@Slf4j
@Singleton
public class GerritClient {
- private static final Injector patchSetInjector = Guice.createInjector(new GerritClientPatchSetInjector());
- private static final Injector commentsInjector = Guice.createInjector(new GerritClientCommentsInjector());
+ private static final String DEFAULT_CHANGE_ID = "DEFAULT_CHANGE_ID";
private GerritClientPatchSet gerritClientPatchSet;
private GerritClientComments gerritClientComments;
public void initialize(Configuration config) {
- gerritClientPatchSet = patchSetInjector.getInstance(GerritClientPatchSet.class);
- gerritClientPatchSet.initialize(config);
- gerritClientComments = commentsInjector.getInstance(GerritClientComments.class);
- gerritClientComments.initialize(config);
+ initialize(config, DEFAULT_CHANGE_ID);
+ }
+
+ public void initialize(Configuration config, String fullChangeId) {
+ log.debug("Initializing client instances for change: {}", fullChangeId);
+ gerritClientPatchSet = SingletonManager.getInstance(GerritClientPatchSet.class, fullChangeId, config);
+ gerritClientComments = SingletonManager.getInstance(GerritClientComments.class, fullChangeId, config);
}
public String getPatchSet(String fullChangeId) throws Exception {
@@ -47,6 +33,7 @@
}
public String getPatchSet(String fullChangeId, boolean isCommentEvent) throws Exception {
+ updateGerritClientPatchSet(fullChangeId);
return gerritClientPatchSet.getPatchSet(fullChangeId, isCommentEvent);
}
@@ -58,24 +45,43 @@
return gerritClientPatchSet.isDisabledTopic(topic);
}
- public HashMap<String, FileDiffProcessed> getFileDiffsProcessed() {
+ public HashMap<String, FileDiffProcessed> getFileDiffsProcessed(String fullChangeId) {
+ updateGerritClientPatchSet(fullChangeId);
return gerritClientPatchSet.getFileDiffsProcessed();
}
- public List<JsonObject> getCommentProperties() {
+ public List<JsonObject> getCommentProperties(String fullChangeId) {
+ updateGerritClientComments(fullChangeId);
return gerritClientComments.getCommentProperties();
}
public void postComments(String fullChangeId, List<HashMap<String, Object>> reviewBatches) throws Exception {
+ updateGerritClientComments(fullChangeId);
gerritClientComments.postComments(fullChangeId, reviewBatches);
}
public boolean retrieveLastComments(Event event, String fullChangeId) {
+ updateGerritClientComments(fullChangeId);
return gerritClientComments.retrieveLastComments(event, fullChangeId);
}
public String getUserPrompt() {
- return gerritClientComments.getUserPrompt(getFileDiffsProcessed());
+ HashMap<String, FileDiffProcessed> fileDiffsProcessed = gerritClientPatchSet.getFileDiffsProcessed();
+ return gerritClientComments.getUserPrompt(fileDiffsProcessed);
+ }
+
+ public void destroy(String fullChangeId) {
+ log.debug("Destroying client instances for change: {}", fullChangeId);
+ SingletonManager.removeInstance(GerritClientPatchSet.class, fullChangeId);
+ SingletonManager.removeInstance(GerritClientComments.class, fullChangeId);
+ }
+
+ private void updateGerritClientPatchSet(String fullChangeId) {
+ gerritClientPatchSet = SingletonManager.getInstance(GerritClientPatchSet.class, fullChangeId);
+ }
+
+ private void updateGerritClientComments(String fullChangeId) {
+ gerritClientComments = SingletonManager.getInstance(GerritClientComments.class, fullChangeId);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientAccount.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientAccount.java
index 2d262d4..f8f3de3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientAccount.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientAccount.java
@@ -13,6 +13,10 @@
@Slf4j
public class GerritClientAccount extends GerritClientBase {
+ public GerritClientAccount(Configuration config) {
+ super(config);
+ }
+
private Optional<Integer> getAccountId(String authorUsername) {
URI uri = URI.create(config.getGerritAuthBaseUrl()
+ UriResourceLocator.gerritAccountIdUri(authorUsername));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientBase.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientBase.java
index 41b906a..3eb9ed9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientBase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientBase.java
@@ -26,7 +26,7 @@
protected HashMap<String, FileDiffProcessed> fileDiffsProcessed = new HashMap<>();
protected Configuration config;
- public void initialize(Configuration config) {
+ public GerritClientBase(Configuration config) {
this.config = config;
config.resetDynamicConfiguration();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientComments.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientComments.java
index 801affd..d719bfe 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientComments.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientComments.java
@@ -7,7 +7,6 @@
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.chatgpt.client.model.ChatGptRequestPoint;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
import lombok.Getter;
@@ -24,11 +23,10 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static com.googlesource.gerrit.plugins.chatgpt.client.ReviewUtils.getTimeStamp;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.ReviewUtils.getTimeStamp;
import static java.net.HttpURLConnection.HTTP_OK;
@Slf4j
-@Singleton
public class GerritClientComments extends GerritClientAccount {
private static final Integer MAX_SECS_GAP_BETWEEN_EVENT_AND_COMMENT = 2;
private static final String BULLET_POINT = "* ";
@@ -39,8 +37,8 @@
@Getter
protected List<JsonObject> commentProperties;
- public void initialize(Configuration config) {
- super.initialize(config);
+ public GerritClientComments(Configuration config) {
+ super(config);
commentProperties = new ArrayList<>();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientPatchSet.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientPatchSet.java
index e440cd3..768798d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientPatchSet.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClientPatchSet.java
@@ -4,7 +4,6 @@
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.chatgpt.client.model.InputFileDiff;
import com.googlesource.gerrit.plugins.chatgpt.client.model.OutputFileDiff;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
@@ -14,17 +13,15 @@
import java.util.*;
@Slf4j
-@Singleton
public class GerritClientPatchSet extends GerritClientAccount {
private final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.create();
-
+ private final List<String> diffs;
private boolean isCommitMessage;
- private List<String> diffs;
- public void initialize(Configuration config) {
- super.initialize(config);
+ public GerritClientPatchSet(Configuration config) {
+ super(config);
diffs = new ArrayList<>();
}
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 e9a5872..882e942 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
@@ -20,6 +20,7 @@
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.ThreadLocalRandom;
@Slf4j
@Singleton
@@ -102,6 +103,9 @@
.messages(messages)
.temperature(config.getGptTemperature())
.stream(config.getGptStreamOutput() && !isCommentEvent)
+ // Seed value is Utilized to prevent ChatGPT from mixing up separate API calls that occur in close
+ // temporal proximity.
+ .seed(ThreadLocalRandom.current().nextInt())
.build();
return gson.toJson(chatCompletionRequest);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/ChatCompletionRequest.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/ChatCompletionRequest.java
index 9817f80..f167afe 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/ChatCompletionRequest.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/model/ChatCompletionRequest.java
@@ -11,6 +11,7 @@
private String model;
private boolean stream;
private double temperature;
+ private int seed;
private List<Message> messages;
@Data
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
index a5fe208..51ec03a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
@@ -131,37 +131,48 @@
return true;
}
- public void handleEvent(Configuration config, Event event) {
- this.config = config;
- PatchSetEvent patchSetEvent = (PatchSetEvent) event;
+ private boolean preprocessEvent(Event event, String fullChangeId, Project.NameKey projectNameKey) {
String eventType = Optional.ofNullable(event.getType()).orElse("");
log.info("Event type {}", eventType);
- Project.NameKey projectNameKey = patchSetEvent.getProjectNameKey();
- BranchNameKey branchNameKey = patchSetEvent.getBranchNameKey();
- Change.Key changeKey = patchSetEvent.getChangeKey();
- String fullChangeId = buildFullChangeId(projectNameKey, branchNameKey, changeKey);
-
- gerritClient.initialize(config);
+ PatchSetEvent patchSetEvent = (PatchSetEvent) event;
if (!isReviewEnabled(patchSetEvent, projectNameKey)) {
- return;
+ return false;
}
switch (eventType) {
case "patchset-created":
if (!isPatchSetReviewEnabled(patchSetEvent)) {
- return;
+ return false;
}
reviewer.setCommentEvent(false);
break;
case "comment-added":
if (!gerritClient.retrieveLastComments(event, fullChangeId)) {
log.info("No comments found for review");
- return;
+ return false;
}
reviewer.setCommentEvent(true);
break;
default:
- return;
+ return false;
+ }
+
+ return true;
+ }
+
+ public void handleEvent(Configuration config, Event event) {
+ this.config = config;
+ PatchSetEvent patchSetEvent = (PatchSetEvent) event;
+ Project.NameKey projectNameKey = patchSetEvent.getProjectNameKey();
+ BranchNameKey branchNameKey = patchSetEvent.getBranchNameKey();
+ Change.Key changeKey = patchSetEvent.getChangeKey();
+ String fullChangeId = buildFullChangeId(projectNameKey, branchNameKey, changeKey);
+
+ gerritClient.initialize(config, fullChangeId);
+
+ if (!preprocessEvent(event, fullChangeId, projectNameKey)) {
+ gerritClient.destroy(fullChangeId);
+ return;
}
// Execute the potentially time-consuming operation asynchronously
@@ -175,6 +186,8 @@
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
+ } finally {
+ gerritClient.destroy(fullChangeId);
}
}, executorService);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/ReviewUtils.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/ReviewUtils.java
similarity index 94%
rename from src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/ReviewUtils.java
rename to src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/ReviewUtils.java
index a3d7998..d27fdb8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/ReviewUtils.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/ReviewUtils.java
@@ -1,4 +1,4 @@
-package com.googlesource.gerrit.plugins.chatgpt.client;
+package com.googlesource.gerrit.plugins.chatgpt.utils;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/SingletonManager.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/SingletonManager.java
new file mode 100644
index 0000000..7d15226
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/SingletonManager.java
@@ -0,0 +1,41 @@
+package com.googlesource.gerrit.plugins.chatgpt.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SingletonManager {
+ private static final Map<String, Object> instances = new HashMap<>();
+
+ private static String getInstanceKey(Class<?> clazz, String id) {
+ return clazz.getName() + ":" + id;
+ }
+
+ public static synchronized <T> T getInstance(Class<T> clazz, String id, Object... constructorArgs) {
+ String key = getInstanceKey(clazz, id);
+ if (!instances.containsKey(key)) {
+ try {
+ // Use reflection to invoke constructor with arguments
+ T instance;
+ if (constructorArgs == null || constructorArgs.length == 0) {
+ instance = clazz.getDeclaredConstructor().newInstance();
+ } else {
+ Class<?>[] argClasses = new Class[constructorArgs.length];
+ for (int i = 0; i < constructorArgs.length; i++) {
+ argClasses[i] = constructorArgs[i].getClass();
+ }
+ instance = clazz.getDeclaredConstructor(argClasses).newInstance(constructorArgs);
+ }
+ instances.put(key, instance);
+ return instance;
+ } catch (Exception e) {
+ throw new RuntimeException("Error creating instance for class: " + clazz.getName(), e);
+ }
+ }
+ return clazz.cast(instances.get(key));
+ }
+
+ public static synchronized void removeInstance(Class<?> clazz, String id) {
+ instances.remove(getInstanceKey(clazz, id));
+ }
+
+}