|
36 | 36 | import com.google.genai.types.FunctionCall; |
37 | 37 | import com.google.genai.types.FunctionResponse; |
38 | 38 | import com.google.genai.types.Part; |
| 39 | +import java.util.HashMap; |
39 | 40 | import java.util.List; |
40 | 41 | import java.util.Map; |
41 | 42 | import java.util.Objects; |
42 | 43 | import java.util.Optional; |
| 44 | +import java.util.concurrent.CountDownLatch; |
| 45 | +import java.util.concurrent.atomic.AtomicReference; |
43 | 46 | import org.junit.Test; |
44 | 47 | import org.junit.runner.RunWith; |
45 | 48 | import org.junit.runners.JUnit4; |
@@ -780,6 +783,63 @@ public void processRequest_notEmptyContent() { |
780 | 783 | assertThat(contents).containsExactly(e.content().get()); |
781 | 784 | } |
782 | 785 |
|
| 786 | + @Test |
| 787 | + public void processRequest_concurrentReadAndWrite_noException() throws Exception { |
| 788 | + LlmAgent agent = |
| 789 | + LlmAgent.builder().name(AGENT).includeContents(LlmAgent.IncludeContents.DEFAULT).build(); |
| 790 | + Session session = |
| 791 | + sessionService |
| 792 | + .createSession("test-app", "test-user", new HashMap<>(), "test-session") |
| 793 | + .blockingGet(); |
| 794 | + |
| 795 | + // Seed with dummy events to widen the race capability |
| 796 | + for (int i = 0; i < 5000; i++) { |
| 797 | + session.events().add(createUserEvent("dummy" + i, "dummy")); |
| 798 | + } |
| 799 | + |
| 800 | + InvocationContext context = |
| 801 | + InvocationContext.builder() |
| 802 | + .invocationId("test-invocation") |
| 803 | + .agent(agent) |
| 804 | + .session(session) |
| 805 | + .sessionService(sessionService) |
| 806 | + .build(); |
| 807 | + |
| 808 | + LlmRequest initialRequest = LlmRequest.builder().build(); |
| 809 | + |
| 810 | + AtomicReference<Throwable> writerError = new AtomicReference<>(); |
| 811 | + CountDownLatch startLatch = new CountDownLatch(1); |
| 812 | + |
| 813 | + Thread writerThread = |
| 814 | + new Thread( |
| 815 | + () -> { |
| 816 | + startLatch.countDown(); |
| 817 | + try { |
| 818 | + for (int i = 0; i < 2000; i++) { |
| 819 | + session.events().add(createUserEvent("writer" + i, "new data")); |
| 820 | + } |
| 821 | + } catch (Throwable t) { |
| 822 | + writerError.set(t); |
| 823 | + } |
| 824 | + }); |
| 825 | + |
| 826 | + writerThread.start(); |
| 827 | + startLatch.await(); // wait for writer to be ready |
| 828 | + |
| 829 | + // Process (read) requests concurrently to trigger race conditions |
| 830 | + for (int i = 0; i < 200; i++) { |
| 831 | + var unused = contentsProcessor.processRequest(context, initialRequest).blockingGet(); |
| 832 | + if (writerError.get() != null) { |
| 833 | + throw new RuntimeException("Writer failed", writerError.get()); |
| 834 | + } |
| 835 | + } |
| 836 | + |
| 837 | + writerThread.join(); |
| 838 | + if (writerError.get() != null) { |
| 839 | + throw new RuntimeException("Writer failed", writerError.get()); |
| 840 | + } |
| 841 | + } |
| 842 | + |
783 | 843 | private static Event createUserEvent(String id, String text) { |
784 | 844 | return Event.builder() |
785 | 845 | .id(id) |
|
0 commit comments