Skip to content

823: replace timer() with Timed - #838

Merged
luoh00 merged 2 commits into
mainfrom
823
Mar 19, 2026
Merged

823: replace timer() with Timed#838
luoh00 merged 2 commits into
mainfrom
823

Conversation

@luoh00

@luoh00 luoh00 commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

823

Description of changes

Removed timer() in LeetcodeClientImpl and LeetcodeAuthStealer and replaced with Spring Timed annotation

Checklist before review

  • I have done a thorough self-review of the PR
  • Copilot has reviewed my latest changes, and all comments have been fixed and/or closed.
  • If I have made database changes, I have made sure I followed all the db repo rules listed in the wiki here. (check if no db changes)
  • All tests have passed
  • I have successfully deployed this PR to staging
  • I have done manual QA in both dev (and staging if possible) and attached screenshots below.

Screenshots

Dev

image

Staging

image image

@luoh00

luoh00 commented Mar 6, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

@github-actions

github-actions Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Available PR Commands

  • /ai - Triggers all AI review commands at once
  • /review - AI review of the PR changes
  • /describe - AI-powered description of the PR
  • /improve - AI-powered suggestions
  • /deploy - Deploy to staging

See: https://github.com/tahminator/codebloom/wiki/CI-Commands

@github-actions

github-actions Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Metrics Verification

Ensure that the @Timed annotation correctly captures and reports metrics for all annotated methods, replicating the functionality of the previously removed manual timer().record() calls. Verify that the metric names and tags are consistent with the intended monitoring setup.

@Timed(value = TIMED_METRIC_NAME)
public LeetcodeQuestion findQuestionBySlug(final String slug) {
    String requestBody;
    try {
        requestBody = SelectProblemQuery.body(slug);
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body");
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);

        int questionId =
                node.path("data").path("question").path("questionId").asInt();
        String questionTitle =
                node.path("data").path("question").path("title").asText();
        String titleSlug =
                node.path("data").path("question").path("titleSlug").asText();
        String link = "https://leetcode.com/problems/" + titleSlug;
        String difficulty =
                node.path("data").path("question").path("difficulty").asText();
        String question = node.path("data").path("question").path("content").asText();

        String statsJson = node.path("data").path("question").path("stats").asText();
        JsonNode stats = mapper.readTree(statsJson);
        String acRateString = stats.get("acRate").asText();
        float acRate = Float.parseFloat(acRateString.replace("%", "")) / 100f;

        JsonNode topicTagsNode = node.path("data").path("question").path("topicTags");

        List<LeetcodeTopicTag> tags = new ArrayList<>();

        for (JsonNode el : topicTagsNode) {
            tags.add(LeetcodeTopicTag.builder()
                    .name(el.get("name").asText())
                    .slug(el.get("slug").asText())
                    .build());
        }

        return LeetcodeQuestion.builder()
                .link(link)
                .questionId(questionId)
                .questionTitle(questionTitle)
                .titleSlug(titleSlug)
                .difficulty(difficulty)
                .question(question)
                .acceptanceRate(acRate)
                .topics(tags)
                .build();
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Override
public ArrayList<LeetcodeSubmission> findSubmissionsByUsername(final String username) {
    return findSubmissionsByUsername(username, 20);
}

@Override
@Timed(value = TIMED_METRIC_NAME)
public ArrayList<LeetcodeSubmission> findSubmissionsByUsername(final String username, final int limit) {
    ArrayList<LeetcodeSubmission> submissions = new ArrayList<>();

    String requestBody;
    try {
        requestBody = SelectAcceptedSubmisisonsQuery.body(username, limit);
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body");
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);
        JsonNode submissionsNode = node.path("data").path("recentAcSubmissionList");

        if (submissionsNode.isArray()) {
            if (submissionsNode.isEmpty() || submissionsNode == null) {
                return submissions;
            }

            for (JsonNode submission : submissionsNode) {
                int id = submission.path("id").asInt();
                String title = submission.path("title").asText();
                String titleSlug = submission.path("titleSlug").asText();
                String timestampString = submission.path("timestamp").asText();
                long epochSeconds = Long.parseLong(timestampString);
                Instant instant = Instant.ofEpochSecond(epochSeconds);

                LocalDateTime timestamp = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
                String statusDisplay = submission.path("statusDisplay").asText();
                submissions.add(new LeetcodeSubmission(id, title, titleSlug, timestamp, statusDisplay));
            }
        }

        return submissions;
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Override
@Timed(value = TIMED_METRIC_NAME)
public LeetcodeDetailedQuestion findSubmissionDetailBySubmissionId(final int submissionId) {
    String requestBody;
    try {
        requestBody = GetSubmissionDetails.body(submissionId);
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body");
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);
        JsonNode baseNode = node.path("data").path("submissionDetails");

        int runtime = baseNode.path("runtime").asInt();
        String runtimeDisplay = baseNode.path("runtimeDisplay").asText();
        float runtimePercentile = (float) baseNode.path("runtimePercentile").asDouble();
        int memory = baseNode.path("memory").asInt();
        String memoryDisplay = baseNode.path("memoryDisplay").asText();
        float memoryPercentile = (float) baseNode.path("memoryPercentile").asDouble();
        String code = baseNode.path("code").asText();
        String langName = baseNode.path("lang").path("name").asText();
        String langVerboseName = baseNode.path("lang").path("verboseName").asText();
        Lang lang = (Strings.isNullOrEmpty(langName) || Strings.isNullOrEmpty(langVerboseName))
                ? null
                : new Lang(langName, langVerboseName);

        // if any of these are empty, then extremely likely that we're throttled.
        if (Strings.isNullOrEmpty(runtimeDisplay) || Strings.isNullOrEmpty(memoryDisplay)) {
            leetcodeAuthStealer.reloadCookie();
        }

        LeetcodeDetailedQuestion question = new LeetcodeDetailedQuestion(
                runtime, runtimeDisplay, runtimePercentile, memory, memoryDisplay, memoryPercentile, code, lang);

        return question;
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Timed(value = TIMED_METRIC_NAME)
public POTD getPotd() {
    String requestBody;
    try {
        requestBody = GetPotd.body();
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body");
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);
        JsonNode baseNode =
                node.path("data").path("activeDailyCodingChallengeQuestion").path("question");

        String titleSlug = baseNode.path("titleSlug").asText();
        String title = baseNode.path("title").asText();
        var difficulty =
                QuestionDifficulty.valueOf(baseNode.path("difficulty").asText());

        return new POTD(title, titleSlug, difficulty);
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Override
@Timed(value = TIMED_METRIC_NAME)
public UserProfile getUserProfile(final String username) {
    String requestBody;
    try {
        requestBody = GetUserProfile.body(username);
    } catch (Exception e) {
        throw new RuntimeException("Error building the request body", e);
    }

    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(requestBody))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException("API Returned status " + statusCode + ": " + body);
        }

        JsonNode node = mapper.readTree(body);
        JsonNode baseNode = node.path("data").path("matchedUser");

        var returnedUsername = baseNode.path("username").asText();
        var ranking = baseNode.path("profile").path("ranking").asText();
        var userAvatar = baseNode.path("profile").path("userAvatar").asText();
        var realName = baseNode.path("profile").path("realName").asText();
        var aboutMe = baseNode.path("profile").path("aboutMe").asText().trim();

        return new UserProfile(returnedUsername, ranking, userAvatar, realName, aboutMe);
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error fetching the API", e);
    }
}

@Override
@Timed(value = TIMED_METRIC_NAME)
public Set<LeetcodeTopicTag> getAllTopicTags() {
    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(GetTopics.body()))
                .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException(
                    "Non-successful response getting topics from Leetcode API. Status code: " + statusCode);
        }

        JsonNode json = mapper.readTree(body);
        JsonNode edges = json.path("data").path("questionTopicTags").path("edges");

        if (!edges.isArray()) {
            throw new RuntimeException("The expected shape of getting topics did not match the received body");
        }

        Set<LeetcodeTopicTag> result = new HashSet<>();

        for (JsonNode edge : edges) {
            JsonNode node = edge.path("node");
            result.add(LeetcodeTopicTag.builder()
                    .name(node.get("name").asText())
                    .slug(node.get("slug").asText())
                    .build());
        }

        return result;
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error getting topics from Leetcode API", e);
    }
}

@Timed(value = TIMED_METRIC_NAME)
public List<LeetcodeQuestion> getAllProblems() {
    try {
        HttpRequest request = getGraphQLRequestBuilder()
                .POST(BodyPublishers.ofString(GetAllProblems.body()))
                .build();
        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();
        if (statusCode != 200) {
            if (isThrottled(statusCode)) {
                leetcodeAuthStealer.reloadCookie();
            }
            throw new RuntimeException(
                    "Non-successful response getting all questions from Leetcode API. Status code: " + statusCode);
        }

        JsonNode json = mapper.readTree(body);
        JsonNode allQuestions =
                json.path("data").path("problemsetQuestionListV2").path("questions");

        if (!allQuestions.isArray()) {
            throw new RuntimeException("The expected shape of getting topics did not match the received body");
        }

        List<LeetcodeQuestion> result = new ArrayList<>();
        for (JsonNode question : allQuestions) {
            JsonNode topicTags = question.get("topicTags");

            List<LeetcodeTopicTag> tags = new ArrayList<>();
            for (JsonNode tag : topicTags) {
                tags.add(LeetcodeTopicTag.builder()
                        .name(tag.get("name").asText())
                        .slug(tag.get("slug").asText())
                        .build());
            }

            result.add(LeetcodeQuestion.builder()
                    .link("https://leetcode.com/problems/"
                            + question.get("titleSlug").asText())
                    .questionId(question.get("questionFrontendId").asInt())
                    .questionTitle(question.get("title").asText())
                    .titleSlug(question.get("titleSlug").asText())
                    .difficulty(question.get("difficulty").asText())
                    .acceptanceRate((float) question.get("acRate").asDouble())
                    .topics(tags)
                    .build());
        }
        return result;
    } catch (Exception e) {
        errorCounter().increment();
        throw new RuntimeException("Error getting all problems from Leetcode API", e);
    }
Metrics Verification

Confirm that the @Timed annotation on methods within LeetcodeAuthStealer accurately records execution times, replacing the previous manual timing mechanism. Pay attention to the stealAuthCookie() method, which is a scheduled task, to ensure its timing is correctly captured.

@Timed(value = METRIC_NAME)
public void stealAuthCookie() {
    boolean acquired = LOCK.writeLock().tryLock();
    if (!acquired) {
        log.info("Lock failed to be acquired, bouncing...");
        return;
    }

    try {
        Auth mostRecentAuth = authRepository.getMostRecentAuth();

        // The auth token should be refreshed every 4 hours.
        if (mostRecentAuth != null
                && mostRecentAuth
                        .getCreatedAt()
                        .isAfter(StandardizedOffsetDateTime.now().minus(4, ChronoUnit.HOURS))) {
            log.info("Auth token already exists, using token from database.");
            cookie = mostRecentAuth.getToken();
            csrf = mostRecentAuth.getCsrf();
            return;
        }

        log.info("falling back to checking redis client...");
        Optional<String> authToken = redisClient.getAuth();

        log.info("auth token in redis = {}", authToken.isPresent());

        if (authToken.isPresent()) {
            log.info("auth token found in redis client");
            cookie = authToken.get();
            csrf = null; // don't care in ci.
            return;
        }

        log.info("auth token not found in redis client");
        log.info("Auth token is missing/expired. Attempting to receive token...");

        stealCookieImpl();
    } finally {
        LOCK.writeLock().unlock();
    }
}

/**
 * There are some cases where leetcode.com may not respect the token anymore. If that is the case, it is best to try
 * to steal a new cookie and replace the current one.
 *
 * <p>You may await the `CompletableFuture` and receive the brand new token, or call-and-forget.
 */
@Async
@Timed(value = METRIC_NAME)
public CompletableFuture<Optional<String>> reloadCookie() {
    boolean acquired = LOCK.writeLock().tryLock();
    if (!acquired) {
        log.info("Lock failed to be acquired, bouncing...");
        return CompletableFuture.completedFuture(Optional.empty());
    }

    try {
        return CompletableFuture.completedFuture(Optional.ofNullable(stealCookieImpl()));
    } finally {
        LOCK.writeLock().unlock();
    }
}

@Timed(value = METRIC_NAME)
public String getCookie() {
    LOCK.readLock().lock();
    try {
        return cookie;
    } finally {
        LOCK.readLock().unlock();
    }
}

/**
 * It's fine if this is null for some requests; it isn't a requirement to fetch data from the GraphQL layer of
 * leetcode.com
 */
@Timed(value = METRIC_NAME)
public String getCsrf() {
    if (csrf == null && !reported) {
        reported = true;
        reporter.log(
                "getCsrf",
                Report.builder()
                        .environments(env.getActiveProfiles())
                        .location(Location.BACKEND)
                        .data(
                                "CSRF token is missing inside of LeetcodeAuthStealer. This may be something to look into.")
                        .build());
    }

    return csrf;
}

@Timed(value = METRIC_NAME)
String stealCookieImpl() {
    Optional<Auth> auth = playwrightClient.getLeetcodeCookie(githubUsername, githubPassword);
    if (auth.isPresent()) {
        var a = auth.get();
        this.csrf = a.getCsrf();
        this.cookie = a.getToken();
        redisClient.setAuth(a.getToken(), 4, ChronoUnit.HOURS);
        log.info("auth token stored in redis");
        this.authRepository.createAuth(Auth.builder()
                .csrf(a.getCsrf())
                .token(a.getToken())
                .createdAt(StandardizedOffsetDateTime.now())
                .build());
        return cookie;
    }
    return null;
}

@luoh00

luoh00 commented Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

@luoh00
luoh00 force-pushed the 823 branch 3 times, most recently from 343ee76 to e5f9948 Compare March 12, 2026 20:03
@luoh00

luoh00 commented Mar 12, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

@luoh00
luoh00 force-pushed the 823 branch 2 times, most recently from fe75355 to df392ee Compare March 12, 2026 21:16
@luoh00

luoh00 commented Mar 12, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Unused Mock

The selectProblemQuery mock object is declared but not utilized in the test class. It should either be used or removed to maintain clean code.

private final SelectProblemQuery selectProblemQuery = mock(SelectProblemQuery.class);
Exception Type

The PR replaces RuntimeException with IllegalArgumentException for various API fetching errors. While IllegalArgumentException is more specific, consider if a custom exception (e.g., LeetcodeApiException or LeetcodeClientException) would better represent errors originating from the Leetcode API, allowing for more granular error handling by callers.

throw new IllegalArgumentException("Error building the request body", e);

Comment thread src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java Outdated
@luoh00

luoh00 commented Mar 15, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

Comment thread src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java Outdated
Comment thread src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java Outdated
@luoh00

luoh00 commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

@github-actions

Copy link
Copy Markdown
Contributor

Title

823: replace timer() with Timed


PR Type

Enhancement, Tests


Description

  • Replaced manual Timer with @Timed annotation.

  • Improved exception handling with LeetcodeClientException.

  • Ensured proper InterruptedException re-interruption.

  • Added tests for InterruptedException scenarios.


Diagram Walkthrough

flowchart LR
  A["Old Timer Implementation"] --> B{Replace with @Timed};
  B -- "Class-level @Timed" --> C["Simplified Metric Collection"];
  D["Generic RuntimeException"] --> E{Improve Error Handling};
  E -- "Specific LeetcodeClientException" --> F["Clearer Exception Types"];
  G["Missing InterruptedException Handling"] --> H{Add Interruption Logic};
  H -- "Thread.currentThread().interrupt()" --> I["Robust Thread Interruption"];
Loading

File Walkthrough

Relevant files
Enhancement
LeetcodeClientImpl.java
Migrate to @Timed annotation and enhance exception handling

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java

  • Replaced manual Timer metric collection with the @Timed annotation at
    the class level.
  • Removed the timer() helper method and the TIMED_METRIC_NAME constant.
  • Updated exception handling to throw LeetcodeClientException instead of
    RuntimeException.
  • Added explicit Thread.currentThread().interrupt() calls when catching
    InterruptedException.
+321/-331
LeetcodeAuthStealer.java
Adopt @Timed annotation for LeetcodeAuthStealer metrics   

src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java

  • Replaced manual Timer metric collection with the @Timed annotation at
    the class level.
  • Removed the timer() helper method, METRIC_NAME constant, and
    meterRegistry field.
  • Simplified method bodies by removing the timer().record(() -> { ... })
    wrappers.
+82/-107
Tests
LeetcodeClientTest.java
Add tests for InterruptedException handling in LeetcodeClient

src/test/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientTest.java

  • Added new test cases to verify InterruptedException handling in
    various LeetcodeClient methods.
  • Ensured that LeetcodeClientException is thrown and the thread's
    interrupted status is correctly set.
  • Added a mock for SelectProblemQuery, though it's not directly used in
    the constructor.
+464/-176

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

**🎫 Ticket compliance analysis **

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Metric Granularity

The class-level @Timed(value = "leetcode.client.execution") annotation replaces a custom timer() method that previously provided more granular metrics by including the class and method name as tags. While the new approach simplifies the code, it might reduce the specificity of monitoring data for individual methods within LeetcodeClientImpl. Consider if method-specific tags are still desired for detailed performance analysis.

@Timed(value = "leetcode.client.execution")
Metric Granularity

Similar to LeetcodeClientImpl, applying @Timed(value = "leetcode.client.execution") at the class level for LeetcodeAuthStealer replaces a custom timer() that provided method-specific metrics. This change might lead to less granular monitoring data for individual methods within this class.

@Timed(value = "leetcode.client.execution")
Unused Mock

The selectProblemQuery mock object is declared but does not appear to be used in the provided test file. This might be a leftover from previous changes or an incomplete test setup.

private final SelectProblemQuery selectProblemQuery = mock(SelectProblemQuery.class);

Comment thread src/test/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientTest.java Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Title

823: replace timer() with Timed


PR Type

Enhancement, Bug fix


Description

  • Replaced custom Timer with Spring's @Timed annotation.

  • Introduced LeetcodeClientException for API-related errors.

  • Improved InterruptedException handling by re-interrupting threads.

  • Refactored generic RuntimeException to specific LeetcodeClientException.


File Walkthrough

Relevant files
Error handling
LeetcodeClientException.java
Introduce custom LeetcodeClientException                                 

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientException.java

  • Added a new custom RuntimeException named LeetcodeClientException.
  • Provides constructors for messages and messages with a cause.
+12/-0   
Enhancement
LeetcodeClientImpl.java
Migrate to @Timed and enhance exception handling                 

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java

  • Replaced manual Timer metric collection with the @Timed annotation at
    the class level.
  • Updated exception handling to throw the new LeetcodeClientException
    instead of generic RuntimeException.
  • Ensured InterruptedException is handled by re-interrupting the current
    thread.
  • Removed the timer() helper method and TIMED_METRIC_NAME constant.
+321/-331
LeetcodeAuthStealer.java
Migrate LeetcodeAuthStealer to @Timed annotation                 

src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java

  • Removed the MeterRegistry dependency and related timer() method.
  • Applied the @Timed annotation at the class level for metric
    collection.
  • Removed manual timer().record() calls from methods.
+82/-109
Tests
LeetcodeClientTest.java
Add InterruptedException tests and format JSON                     

src/test/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientTest.java

  • Added new test cases to verify correct handling of
    InterruptedException.
  • Asserted that LeetcodeClientException is thrown and the thread's
    interrupted status is set.
  • Minor formatting adjustments to JSON string literals.
+462/-176
LeetcodeAuthStealerTest.java
Remove MeterRegistry from LeetcodeAuthStealer tests           

src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java

  • Removed MeterRegistry from the class fields and constructor.
  • Updated the instantiation of LeetcodeAuthStealer to reflect the
    removal of MeterRegistry.
+2/-6     

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

**🎫 Ticket compliance analysis **

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Exception Handling

The PR introduces a new custom exception LeetcodeClientException and explicitly handles InterruptedException by setting and clearing the interrupted status. This is a good practice, but ensure that all potential InterruptedException scenarios are covered and that the LeetcodeClientException is handled appropriately upstream where these methods are called.

} catch (InterruptedException e) {
    errorCounter().increment();
    Thread.currentThread().interrupt();
    throw new LeetcodeClientException("Thread interrupted", e);
} catch (Exception e) {
    errorCounter().increment();
    throw new LeetcodeClientException("Error fetching the API", e);
}

@github-actions

Copy link
Copy Markdown
Contributor

Title

823: replace timer() with Timed


PR Type

Enhancement, Tests


Description

  • Replace custom timer with Spring's @Timed annotation.

  • Introduce LeetcodeClientException for API errors.

  • Improve InterruptedException handling with re-interruption.

  • Add tests for InterruptedException scenarios.


Diagram Walkthrough

flowchart LR
  A[LeetcodeClientImpl] -- "Replace custom timer" --> B{Use @Timed annotation};
  A -- "Introduce custom exception" --> C[LeetcodeClientException];
  A -- "Handle InterruptedException" --> D[Re-interrupt thread];
  E[LeetcodeAuthStealer] -- "Replace custom timer" --> B;
  F[LeetcodeClientTest] -- "Add InterruptedException tests" --> A;
  G[LeetcodeAuthStealerTest] -- "Remove MeterRegistry" --> E;
Loading

File Walkthrough

Relevant files
Error handling
LeetcodeClientException.java
Introduce custom Leetcode client exception                             

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientException.java

  • Create a new RuntimeException subclass, LeetcodeClientException.
  • Provide constructors for messages and messages with a cause.
+12/-0   
Enhancement
LeetcodeClientImpl.java
Adopt `@Timed` annotation and enhance exception handling 

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java

  • Remove manual Timer usage and TIMED_METRIC_NAME.
  • Apply @Timed(value = "leetcode.client.execution") at the class level
    for automatic method timing.
  • Replace generic RuntimeException with LeetcodeClientException for
    API-related errors.
  • Implement specific handling for InterruptedException, including
    Thread.currentThread().interrupt().
+321/-331
LeetcodeAuthStealer.java
Refactor Leetcode auth stealer to use `@Timed`                     

src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java

  • Remove MeterRegistry and Timer imports and their manual usage.
  • Apply @Timed(value = "leetcode.client.execution") at the class level
    and explicitly on stealCookieImpl().
  • Remove meterRegistry from the constructor.
  • Remove timer().record() wrappers from all methods.
+83/-109
Tests
LeetcodeClientTest.java
Add InterruptedException tests and format JSON                     

src/test/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientTest.java

  • Add new test cases to verify InterruptedException handling in various
    client methods.
  • Assert that LeetcodeClientException is thrown and the thread's
    interrupted status is set.
  • Update JSON string formatting to use text blocks for readability.
+462/-176
LeetcodeAuthStealerTest.java
Remove MeterRegistry from auth stealer tests                         

src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java

  • Remove MeterRegistry from the test class constructor.
  • Update the LeetcodeAuthStealer instantiation in setup() to reflect the
    removed MeterRegistry dependency.
+2/-6     

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

**🎫 Ticket compliance analysis **

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Redundant Annotation

The @Timed annotation is applied at both the class level and on the stealCookieImpl() method with the same value. The class-level annotation should suffice for all public methods, making the method-level annotation redundant unless specific overriding behavior or different tags are intended.

@Timed(value = "leetcode.client.execution")
public class LeetcodeAuthStealer {

    @VisibleForTesting
    // CHECKSTYLE:OFF
    final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
    // CHECKSTYLE:ON

    private volatile String cookie;
    private volatile String csrf;

    /** So we don't report that a csrf token is missing more than once per machine lifecycle. */
    private boolean reported = false;

    @Value("${github.username}")
    private String githubUsername;

    @Value("${github.password}")
    private String githubPassword;

    private final RedisClient redisClient;
    private final AuthRepository authRepository;
    private final Reporter reporter;
    private final Env env;
    private final PlaywrightClient playwrightClient;

    public LeetcodeAuthStealer(
            final RedisClient redisClient,
            final AuthRepository authRepository,
            final Reporter reporter,
            final Env env,
            PlaywrightClient playwrightClient) {
        this.redisClient = redisClient;
        this.authRepository = authRepository;
        this.reporter = reporter;
        this.env = env;
        this.playwrightClient = playwrightClient;
    }

    /**
     * <b>DO NOT RETURN THE TOKEN IN ANY API ENDPOINT.</b> <div /> This function utilizes Playwright in order to get an
     * authentication key from Leetcode. That code is stored in the database and can then be used to run authenticated
     * queries such as being used to retrieve code from our user submissions.
     */
    @Scheduled(initialDelay = 0, fixedDelay = 1, timeUnit = TimeUnit.HOURS)
    public void stealAuthCookie() {
        boolean acquired = LOCK.writeLock().tryLock();
        if (!acquired) {
            log.info("Lock failed to be acquired, bouncing...");
            return;
        }

        try {
            Auth mostRecentAuth = authRepository.getMostRecentAuth();

            // The auth token should be refreshed every 4 hours.
            if (mostRecentAuth != null
                    && mostRecentAuth
                            .getCreatedAt()
                            .isAfter(StandardizedOffsetDateTime.now().minus(4, ChronoUnit.HOURS))) {
                log.info("Auth token already exists, using token from database.");
                cookie = mostRecentAuth.getToken();
                csrf = mostRecentAuth.getCsrf();
                return;
            }

            log.info("falling back to checking redis client...");
            Optional<String> authToken = redisClient.getAuth();

            log.info("auth token in redis = {}", authToken.isPresent());

            if (authToken.isPresent()) {
                log.info("auth token found in redis client");
                cookie = authToken.get();
                csrf = null; // don't care in ci.
                return;
            }

            log.info("auth token not found in redis client");
            log.info("Auth token is missing/expired. Attempting to receive token...");

            stealCookieImpl();
        } finally {
            LOCK.writeLock().unlock();
        }
    }

    /**
     * There are some cases where leetcode.com may not respect the token anymore. If that is the case, it is best to try
     * to steal a new cookie and replace the current one.
     *
     * <p>You may await the `CompletableFuture` and receive the brand new token, or call-and-forget.
     */
    @Async
    public CompletableFuture<Optional<String>> reloadCookie() {
        boolean acquired = LOCK.writeLock().tryLock();
        if (!acquired) {
            log.info("Lock failed to be acquired, bouncing...");
            return CompletableFuture.completedFuture(Optional.empty());
        }

        try {
            return CompletableFuture.completedFuture(Optional.ofNullable(stealCookieImpl()));
        } finally {
            LOCK.writeLock().unlock();
        }
    }

    public String getCookie() {
        LOCK.readLock().lock();
        try {
            return cookie;
        } finally {
            LOCK.readLock().unlock();
        }
    }

    /**
     * It's fine if this is null for some requests; it isn't a requirement to fetch data from the GraphQL layer of
     * leetcode.com
     */
    public String getCsrf() {
        if (csrf == null && !reported) {
            reported = true;
            reporter.log(
                    "getCsrf",
                    Report.builder()
                            .environments(env.getActiveProfiles())
                            .location(Location.BACKEND)
                            .data(
                                    "CSRF token is missing inside of LeetcodeAuthStealer. This may be something to look into.")
                            .build());
        }

        return csrf;
    }

    @Timed(value = "leetcode.client.execution")

@luoh00

luoh00 commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

@luoh00
luoh00 requested a review from tahminator March 17, 2026 21:50
Comment thread src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Title

823: replace timer() with Timed


PR Type

Enhancement, Tests


Description

  • Replaced manual Timer with @Timed annotation

  • Introduced LeetcodeClientException for specific errors

  • Implemented proper InterruptedException handling

  • Added tests for InterruptedException scenarios


Diagram Walkthrough

flowchart LR
  A["Old Timer Logic"] -- "Replaced with" --> B["@Timed Annotation"];
  C["Generic RuntimeException"] -- "Replaced with" --> D["LeetcodeClientException"];
  E["LeetcodeClientImpl"] -- "Uses" --> B;
  F["LeetcodeAuthStealer"] -- "Uses" --> B;
  E -- "Throws" --> D;
  F -- "Throws" --> D;
Loading

File Walkthrough

Relevant files
Error handling
LeetcodeClientException.java
Introduce custom LeetcodeClientException                                 

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientException.java

  • Created a new custom RuntimeException named LeetcodeClientException.
  • Provides constructors for messages and messages with a cause.
+12/-0   
Enhancement
LeetcodeClientImpl.java
Migrate to @Timed and enhance exception handling                 

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java

  • Replaced manual Timer usage with the @Timed annotation at the class
    level.
  • Updated exception handling to throw LeetcodeClientException instead of
    generic RuntimeException.
  • Added specific catch (InterruptedException e) blocks to re-interrupt
    the thread and throw LeetcodeClientException.
+321/-331
LeetcodeAuthStealer.java
Migrate to @Timed and remove MeterRegistry                             

src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java

  • Replaced manual Timer usage with the @Timed annotation at the class
    level.
  • Removed the MeterRegistry dependency as it's no longer needed for
    manual timing.
  • Removed the timer() helper method and METRIC_NAME constant.
+82/-109
Tests
LeetcodeClientTest.java
Add InterruptedException tests for LeetcodeClient               

src/test/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientTest.java

  • Added new test cases to verify InterruptedException handling in all
    LeetcodeClient methods.
  • Ensured that InterruptedException leads to LeetcodeClientException and
    re-interrupts the thread.
  • Minor formatting adjustments to JSON response strings.
+462/-176
LeetcodeAuthStealerTest.java
Remove unused MeterRegistry from LeetcodeAuthStealer tests

src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java

  • Removed MeterRegistry from the test class and its setup.
  • Updated the LeetcodeAuthStealer constructor call to reflect the
    removal of MeterRegistry.
+2/-6     

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

**🎫 Ticket compliance analysis **

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Metrics Tagging

The manual timer() method explicitly added class and method tags. While @Timed applied at the class level should provide similar default tags, it's good practice to confirm that the desired granularity and naming conventions for metrics are maintained in the monitoring system.

@Timed(value = "leetcode.client.execution")
Error Handling

The PR introduces a new custom LeetcodeClientException which extends RuntimeException. While this provides more specific error types, ensure that all existing call sites of LeetcodeClient methods are compatible with this change and handle the new exception type as intended, or that it's expected to propagate as a runtime exception.

String requestBody;
try {
    requestBody = SelectProblemQuery.body(slug);
} catch (Exception e) {
    throw new LeetcodeClientException("Error building the request body", e);
}

@github-actions

Copy link
Copy Markdown
Contributor

Title

823: replace timer() with Timed


PR Type

Enhancement, Refactoring, Tests


Description

  • Introduce LeetcodeClientException for specific errors.

  • Replace manual Timer with @Timed annotation.

  • Improve InterruptedException handling with re-interruption.

  • Add tests for new exception handling.


Diagram Walkthrough

flowchart LR
  OldTiming["Manual Timer Logic"] --> |Replaced by| TimedAnnotation["@Timed Annotation"]
  OldExceptions["Generic RuntimeException"] --> |Replaced by| NewException["LeetcodeClientException"]
  LeetcodeClientImpl["LeetcodeClientImpl"] -- Uses --> TimedAnnotation
  LeetcodeClientImpl -- Throws --> NewException
  LeetcodeClientImpl -- Handles --> InterruptedException["InterruptedException"]
  LeetcodeAuthStealer["LeetcodeAuthStealer"] -- Uses --> TimedAnnotation
  LeetcodeClientTest["LeetcodeClientTest"] -- Verifies --> NewException
  LeetcodeClientTest -- Verifies --> InterruptedException
  LeetcodeAuthStealerTest["LeetcodeAuthStealerTest"] -- Cleans up --> MeterRegistryRemoval["MeterRegistry Removal"]
Loading

File Walkthrough

Relevant files
Error handling
LeetcodeClientException.java
Introduce custom Leetcode client exception                             

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientException.java

  • Introduced a new custom RuntimeException named
    LeetcodeClientException.
  • Provides constructors for messages and nested exceptions.
+12/-0   
Refactoring
LeetcodeClientImpl.java
Refactor timing and exception handling in Leetcode client

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java

  • Replaced manual Timer usage with the @Timed annotation at the class
    level.
  • Updated exception handling to throw LeetcodeClientException instead of
    generic RuntimeException.
  • Added explicit InterruptedException handling, including
    Thread.currentThread().interrupt().
+321/-331
LeetcodeAuthStealer.java
Refactor timing and remove unused metrics in auth stealer

src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java

  • Replaced manual Timer usage with the @Timed annotation at the class
    level.
  • Removed the meterRegistry field and related constructor parameter.
  • Eliminated the timer() private method and METRIC_NAME constant.
+82/-109
Tests
LeetcodeClientTest.java
Add InterruptedException tests for Leetcode client             

src/test/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientTest.java

  • Added new test cases to verify InterruptedException handling in all
    client methods.
  • Ensured that LeetcodeClientException is thrown and the thread is
    re-interrupted.
  • Minor formatting adjustments to JSON response strings.
+462/-176
LeetcodeAuthStealerTest.java
Clean up unused MeterRegistry in auth stealer tests           

src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java

  • Removed MeterRegistry related imports and field from the test class.
  • Updated the constructor and setup() method to reflect the removal of
    meterRegistry.
+2/-6     

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

Remove-hacky-timer-in-LeetcodeClient-3167c85563aa80cc9723db548794fa2c - Fully compliant

Compliant requirements:

  • Removed the custom timer() utility from LeetcodeClientImpl.
  • Removed the custom timer() utility from LeetcodeAuthStealer.
  • Replaced the custom timer functionality with Spring's @Timed annotation for method execution timing.
  • Ensured proper error handling for InterruptedException by re-interrupting the thread.
  • Introduced a custom exception LeetcodeClientException for Leetcode client-related errors.
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Error Handling

The PR correctly introduces LeetcodeClientException and handles InterruptedException by re-interrupting the thread. This is a good pattern for propagating interruption status. Ensure that all methods performing I/O operations that might throw InterruptedException are consistently updated to follow this pattern.

} catch (InterruptedException e) {
    errorCounter().increment();
    Thread.currentThread().interrupt();
    throw new LeetcodeClientException("Thread interrupted", e);
Timed Annotation Scope

The @Timed annotation is applied at the class level for LeetcodeClientImpl and LeetcodeAuthStealer. This means all public methods within these classes will be timed. While this aligns with replacing the previous timer() wrapper, consider if there are any specific public methods that should not be timed or require a different metric name.

@Timed(value = "leetcode.client.execution")

@luoh00

luoh00 commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

luoh00 added 2 commits March 19, 2026 15:30
Also replaced generic RuntimeException with more specific
IllegalArgumentException and reinterrupt on InterruptedException
Removed unused meterRegistry from LeetcodeAuthStealer tests
Add VisibleForTesting annotation
@github-actions

Copy link
Copy Markdown
Contributor

Title

823: replace timer() with Timed


PR Type

Enhancement, Tests


Description

  • Migrate to Spring's @Timed for metric collection.

  • Introduce LeetcodeClientException for API errors.

  • Improve InterruptedException handling in client.

  • Add comprehensive tests for new exception flows.


Diagram Walkthrough

flowchart LR
    A["LeetcodeClientImpl"]
    B["LeetcodeAuthStealer"]
    C["Custom Timer"]
    D["@Timed Annotation"]
    E["RuntimeException"]
    F["LeetcodeClientException"]
    G["InterruptedException Handling"]
    H["LeetcodeClientTest"]

    A -- "Replaced with" --> D
    B -- "Replaced with" --> D
    C -- "Removed" --> D
    A -- "Throws" --> F
    E -- "Replaced by" --> F
    A -- "Implements" --> G
    H -- "Tests" --> F
    H -- "Tests" --> G
Loading

File Walkthrough

Relevant files
Error handling
LeetcodeClientException.java
Introduce custom Leetcode client exception                             

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientException.java

  • Introduced a new custom RuntimeException named
    LeetcodeClientException.
  • Provides constructors for messages and messages with a cause.
+12/-0   
Enhancement
LeetcodeClientImpl.java
Migrate to @Timed and enhance exception handling                 

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java

  • Replaced manual Timer usage with the @Timed annotation at the class
    level.
  • Removed the timer() private method and MeterRegistry from the
    constructor.
  • Replaced generic RuntimeException with LeetcodeClientException in all
    catch blocks.
  • Added explicit InterruptedException handling, re-interrupting the
    current thread.
+321/-331
LeetcodeAuthStealer.java
Migrate to @Timed and refine internal methods                       

src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java

  • Replaced manual Timer usage with the @Timed annotation at the class
    level.
  • Removed the timer() private method and MeterRegistry from the
    constructor.
  • Removed METRIC_NAME constant.
  • Applied @VisibleForTesting to the stealCookieImpl() method.
+83/-109
Tests
LeetcodeClientTest.java
Add tests for new exception handling                                         

src/test/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientTest.java

  • Added new test cases to verify LeetcodeClientException is thrown for
    various API calls.
  • Included tests to ensure InterruptedException is caught, the thread is
    re-interrupted, and LeetcodeClientException is thrown.
+462/-176
LeetcodeAuthStealerTest.java
Remove MeterRegistry from tests                                                   

src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java

  • Removed MeterRegistry related imports, fields, and constructor
    parameters.
  • Updated the LeetcodeAuthStealer instantiation to reflect the removal
    of MeterRegistry.
+2/-6     

@luoh00

luoh00 commented Mar 19, 2026

Copy link
Copy Markdown
Contributor Author

/deploy

@github-actions

Copy link
Copy Markdown
Contributor

Title

823: replace timer() with Timed


PR Type

Enhancement, Bug fix, Tests


Description

  • Replaced custom timer with Spring's @Timed annotation.

  • Introduced LeetcodeClientException for API-related errors.

  • Improved InterruptedException handling with re-interruption.

  • Added tests for new exception handling scenarios.


Diagram Walkthrough

flowchart LR
  A[LeetcodeClientImpl] -- "Replaced manual timing" --> B{Spring @Timed Annotation};
  C[LeetcodeAuthStealer] -- "Replaced manual timing" --> B;
  D[Old RuntimeException] -- "Replaced with" --> E[LeetcodeClientException];
  F[InterruptedException] -- "Added handling for" --> E;
  G[LeetcodeClientTest] -- "Added tests for" --> F;
Loading

File Walkthrough

Relevant files
Error handling
LeetcodeClientException.java
Add custom Leetcode client exception                                         

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientException.java

  • Introduced LeetcodeClientException extending RuntimeException.
  • Provides constructors for messages and messages with a cause.
+12/-0   
Enhancement
LeetcodeClientImpl.java
Integrate @Timed and refine exception handling                     

src/main/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientImpl.java

  • Replaced manual Timer usage with the @Timed annotation at the class
    level.
  • Removed the private timer() method.
  • Replaced generic RuntimeException with LeetcodeClientException in
    catch blocks.
  • Added specific handling for InterruptedException, re-interrupting the
    current thread.
+321/-331
LeetcodeAuthStealer.java
Refactor LeetcodeAuthStealer for @Timed and testability   

src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java

  • Applied the @Timed annotation to the class for metric collection.
  • Removed the meterRegistry field and its related timer() method.
  • Removed manual timer().record() wrappers from methods.
  • Added @VisibleForTesting to the stealCookieImpl() method.
+83/-109
Tests
LeetcodeClientTest.java
Add InterruptedException tests for LeetcodeClient               

src/test/java/org/patinanetwork/codebloom/common/leetcode/LeetcodeClientTest.java

  • Added new test cases to verify InterruptedException handling in all
    client methods.
  • Ensured that LeetcodeClientException is thrown and the thread's
    interrupted status is set.
  • Verified that Thread.interrupted() clears the interrupted status after
    handling.
+462/-176
LeetcodeAuthStealerTest.java
Remove MeterRegistry from LeetcodeAuthStealer tests           

src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java

  • Removed MeterRegistry related imports, fields, and constructor
    parameters.
  • Updated the test setup to reflect the removal of meterRegistry from
    the LeetcodeAuthStealer class.
+2/-6     

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

823 - Fully compliant

Compliant requirements:

  • Removed timer() in LeetcodeClientImpl.
  • Removed timer() in LeetcodeAuthStealer.
  • Replaced with Spring @Timed annotation.
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Test Formatting

The JSON string literals in the test methods have been reformatted, causing a large number of lines to appear as changed in the diff. While this is a cosmetic change and does not affect functionality, it adds noise to the review. Consider if such formatting changes are necessary or if they should be applied consistently across the codebase in a separate PR.

{
  "data": {
    "question": {
      "questionId": "42",
      "title": "Trapping Rain Water",
      "titleSlug": "trapping-rain-water",
      "difficulty": "Hard",
      "content": "<p>Given n non-negative integers...</p>",
      "stats": "{\\"acRate\\":\\"49.4%\\"}",
      "topicTags": [
        {"name": "Array", "slug": "array"},
        {"name": "Two Pointers", "slug": "two-pointers"}
      ]
    }
  }
}
Class-level @Timed

The @Timed annotation is applied at the class level for LeetcodeClientImpl and LeetcodeAuthStealer. This means all public methods within these classes will be timed. This is generally the desired behavior when replacing manual timer calls, but it's worth confirming that there are no specific public methods that should explicitly not be timed, as the annotation would need to be moved to individual methods in such a case.

@Timed(value = "leetcode.client.execution")
InterruptedException Handling

The explicit handling of InterruptedException by calling Thread.currentThread().interrupt() and re-throwing a LeetcodeClientException is a good practice. This ensures that the interrupted status of the thread is preserved, allowing higher-level code to react appropriately. This is consistently applied across all methods.

} catch (InterruptedException e) {
    errorCounter().increment();
    Thread.currentThread().interrupt();
    throw new LeetcodeClientException("Thread interrupted", e);

@luoh00
luoh00 merged commit 206529d into main Mar 19, 2026
35 checks passed
@luoh00
luoh00 deleted the 823 branch March 19, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants