Task to check#236
Conversation
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am requesting changes because there are a few critical issues that will prevent the solution from matching the required behavior and expected output. First, AsyncRequestProcessor references UserData, but there is no confirmed UserData class with the required fields and toString() format; without this, the project may not compile or the printed lines will not match the expected UserData[userId=..., details=...] format (add userId, details fields and override toString() exactly as specified). Second, the details text is currently generated in Polish ("Szczegóły dla użytkownika " + userId), while the task requires English "Details for " + userId, and in Main the prefix "Przetworzono: " must be changed to "Processed: " to match the expected console output. Third, because you only put results into the cache after processing completes, two concurrent calls for the same userId can both start expensive work; you should instead cache an in-progress CompletableFuture<UserData> (e.g., via a Map<String, CompletableFuture<UserData>> and computeIfAbsent) so duplicates share the same future. Finally, printing directly inside thenAccept makes output ordering depend on completion timing; to guarantee the exact required order, collect futures (keeping their original indices) and print all results after CompletableFuture.allOf(...).join(). Once these are fixed, your asynchronous design and cache usage will align well with the task requirements—your current structure is solid, it just needs these adjustments to be fully correct.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
| public class AsyncRequestProcessor { | ||
|
|
||
| private final Executor executor; | ||
| private final Map<String, UserData> cache = new ConcurrentHashMap<>(); |
There was a problem hiding this comment.
This file references UserData in the cache declaration. Ensure a UserData class exists with fields userId and details, and override toString() to return exactly UserData[userId=..., details=...] so output matches the task's expected console lines.
| simulateDelay(); | ||
|
|
||
| UserData userData = | ||
| new UserData(userId, "Szczegóły dla użytkownika " + userId); |
There was a problem hiding this comment.
The details string is created in Polish: "Szczegóły dla użytkownika " + userId. The task's expected output uses English Details for <userId>. Change this to "Details for " + userId so the produced UserData contains the expected text.
| if (cachedData != null) { | ||
| return CompletableFuture.completedFuture(cachedData); | ||
| } | ||
|
|
There was a problem hiding this comment.
You check the cache before starting async work, but only call cache.put(...) after processing completes. This allows two concurrent requests for the same userId to both perform the expensive work. To meet the requirement that repeated requests be served from cache and reduce response time, consider caching the in-progress CompletableFuture<UserData> (e.g. a Map<String, CompletableFuture<UserData>> or computeIfAbsent) so subsequent requests reuse the same future.
|
|
||
| // Simulating multiple concurrent requests | ||
| String[] userIds = {"user1", "user2", "user3", "user1"}; // Note: "user1" is repeated | ||
| String[] userIds = {"user1", "user2", "user3", "user1"}; |
There was a problem hiding this comment.
The input array matches the requirement ({"user1", "user2", "user3", "user1"}). If the tests expect the exact ordering from the spec, avoid printing inside thenAccept. Instead collect results together with their original index and print them in input order after CompletableFuture.allOf(...).join() so the console sequence matches the required output.
| String userId = userIds[i]; | ||
| futures[i] = asyncRequestProcessor.processRequest(userId) | ||
| .thenAccept(userData -> System.out.println("Processed: " + userData)); | ||
| .thenAccept(userData -> |
There was a problem hiding this comment.
You're printing the result inside the completion callback which makes output ordering depend on completion timing. To guarantee the exact required ordering, gather results and print after all futures complete rather than printing directly inside thenAccept. This will ensure deterministic output order.
| futures[i] = asyncRequestProcessor.processRequest(userId) | ||
| .thenAccept(userData -> System.out.println("Processed: " + userData)); | ||
| .thenAccept(userData -> | ||
| System.out.println("Przetworzono: " + userData)); |
There was a problem hiding this comment.
The printed prefix is Polish ("Przetworzono: "). Change it to "Processed: " so the console lines match the expected output format in the task description (e.g. Processed: UserData[userId=user1, details=Details for user1]).
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am requesting changes because a few core assignment requirements are not yet met, and they directly affect both functionality and grading. In AsyncRequestProcessor.java, the cache is currently Map<String, CompletableFuture<UserData>>, but the task explicitly requires Map<String, UserData> cache = new ConcurrentHashMap<>(); and that you first check this cache for a completed UserData and return CompletableFuture.completedFuture(cached) when present ([CHECKLIST ITEMS #10–#13]). To fix this, keep the required Map<String, UserData> cache for finished results and add a separate Map<String, CompletableFuture<UserData>> inFlight using computeIfAbsent so concurrent calls for the same id share the same future; when the future completes, store the UserData into cache and remove the entry from inFlight. In Main.java, the console output prefix is currently Polish ("Przetworzono: "), but the expected exact text is "Processed: ", so the line that prints results must be updated to use that exact prefix ([CHECKLIST ITEMS #7, #17]). Additionally, printing only after CompletableFuture.allOf(...).join() means results appear in the input order, whereas the task expects faster/cached results to appear earlier; update the printing logic so each result is printed as its future completes (e.g., via thenAccept or similar). Once you adjust the cache design and the output text/ordering, your solution will align well with the requirements—your current use of CompletableFuture and UserData construction is otherwise solid and on the right track.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
| public class AsyncRequestProcessor { | ||
|
|
||
| private final Executor executor; | ||
| private final Map<String, CompletableFuture<UserData>> cache = |
There was a problem hiding this comment.
The cache is declared as Map<String, CompletableFuture<UserData>>. The requirements explicitly require Map<String, UserData> cache = new ConcurrentHashMap<>(); (checklist items #10 and #11). Change the declaration to store UserData results as required. If you need to avoid duplicate work, keep a separate Map<String, CompletableFuture<UserData>> inFlight for in-progress operations instead of changing the required cache type.
|
|
||
| public CompletableFuture<UserData> processRequest(String userId) { | ||
| return null; | ||
| return cache.computeIfAbsent(userId, id -> |
There was a problem hiding this comment.
Using computeIfAbsent on this cache stores in-progress CompletableFutures in the result cache. That prevents you from implementing the required behavior of checking the cache for a completed UserData and returning it immediately (checklist item #12). Instead: (a) check the Map<String, UserData> cache first and return CompletableFuture.completedFuture(cached) when present; (b) for new work, computeIfAbsent on a separate inFlight map of CompletableFuture<UserData> so concurrent callers share the same future (recommended by checklist item #18).
| CompletableFuture.supplyAsync(() -> { | ||
| simulateDelay(); | ||
| return new UserData(id, "Details for " + id); |
There was a problem hiding this comment.
This lambda constructs the UserData correctly ("Details for " + id) which matches checklist item #16 — good. However, after the UserData is produced you must store the result into the required Map<String, UserData> cache (checklist item #13). Consider attaching a whenComplete/thenApply handler on the in-flight future to put the finished UserData into cache and remove the entry from the in-flight map.
| CompletableFuture.supplyAsync(() -> { | ||
| simulateDelay(); | ||
| return new UserData(id, "Details for " + id); | ||
| }, executor) |
There was a problem hiding this comment.
Overall: to satisfy all requirements, add a Map<String, UserData> cache = new ConcurrentHashMap<>(); and a separate Map<String, CompletableFuture<UserData>> inFlight = new ConcurrentHashMap<>();. processRequest should (1) return a completed future immediately if cache contains the id, or (2) use inFlight.computeIfAbsent to start or share the async work, then on completion store the resulting UserData into cache. This preserves thread-safety and avoids duplicate expensive operations (addresses checklist items #12, #13 and #18).
| } | ||
|
|
||
| // Wait for all futures to complete | ||
| CompletableFuture.allOf(futures).join(); |
There was a problem hiding this comment.
Using CompletableFuture.allOf(...).join() and then iterating the futures array prints results in the original input order. The expected output in the task requires cached/fast results to appear earlier — print results as each future completes (e.g., attach a completion handler) or otherwise change the logic to produce the required ordering.
| CompletableFuture.allOf(futures).join(); | ||
|
|
||
| for (CompletableFuture<UserData> future : futures) { | ||
| System.out.println("Przetworzono: " + future.join()); |
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am approving your solution as an educational override so you can keep progressing while you refine your understanding of asynchronous execution and thread pools. Your AsyncRequestProcessor is very well done: you correctly use a ConcurrentHashMap for the cache, share in-flight CompletableFutures so duplicate requests don’t trigger extra work, and store results into the cache when processing completes. In Main.java, you correctly iterate over the given userIds array, call processRequest(userId), and print each result with the required "Processed: " prefix as the futures complete.
One important improvement: in src/main/java/mate/academy/Main.java, after submitting all tasks and attaching thenAccept, you call executor.shutdown() immediately, but you don’t wait for completion. This can, in some environments, allow the JVM to exit before all thenAccept callbacks run, so some Processed: ... lines might not print. A good fix would be to either: (1) collect all returned CompletableFuture<UserData> into a list and call CompletableFuture.allOf(list.toArray(...)).join() before shutdown, or (2) call executor.shutdown(); executor.awaitTermination(timeout, unit); to wait for all tasks to finish. Overall, your asynchronous design and caching logic are strong, and with this small lifecycle fix your solution will be robust and production-ready—nice work, keep it up!
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Footnotes
|
|
||
| // Wait for all futures to complete | ||
| CompletableFuture.allOf(futures).join(); | ||
| executor.shutdown(); |
There was a problem hiding this comment.
Calling executor.shutdown() immediately may not wait for the submitted asynchronous tasks to finish; the JVM can exit before all thenAccept prints run. Collect the returned CompletableFuture<UserData> instances (e.g., in a list) and wait for them to complete with CompletableFuture.allOf(...).join() or use executor.shutdown(); executor.awaitTermination(...) before exiting to ensure all results are printed.
No description provided.