Skip to content

Commit f535428

Browse files
committed
Make RecordingDownloader more resillient against ReCaptchaExceptions
Implement a max number of requests per minute to prevent hitting reate limits and triggering ReCaptchaExceptions. This slows down the RecordingDownloader significantly and can be adjusted if needed. A request ist retried once when facing a ReCaptchaException.
1 parent 667c867 commit f535428

1 file changed

Lines changed: 52 additions & 0 deletions

File tree

extractor/src/test/java/org/schabi/newpipe/downloader/RecordingDownloader.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.nio.file.Files;
1616
import java.nio.file.Path;
1717
import java.nio.file.Paths;
18+
import java.util.Random;
1819

1920
import javax.annotation.Nonnull;
2021

@@ -46,6 +47,18 @@ class RecordingDownloader extends Downloader {
4647
private int index = 0;
4748
private final String path;
4849

50+
// try to prevent ReCaptchaExceptions / rate limits by tracking and throttling the requests
51+
/**
52+
* The maximum number of requests per minutes which are executed
53+
* by the {@link RecordingDownloader}.
54+
* The values can be adjusted when executing the downloader and running into problems.
55+
* <p>TODO: Allow adjusting the value by setting a param in the gradle command</p>
56+
*/
57+
private static final int MAX_REQUESTS_PER_MINUTE = 30;
58+
private static final long[] requestTimes = new long[MAX_REQUESTS_PER_MINUTE];
59+
private static int requestTimesCursor = -1;
60+
private static final Random throttleRandom = new Random();
61+
4962
/**
5063
* Creates the folder described by {@code stringPath} if it does not exists.
5164
* Deletes existing files starting with {@link RecordingDownloader#FILE_NAME_PREFIX}.
@@ -69,6 +82,45 @@ public RecordingDownloader(final String stringPath) throws IOException {
6982
@Override
7083
public Response execute(@Nonnull final Request request) throws IOException,
7184
ReCaptchaException {
85+
86+
// Delay the execution if the max number of requests per minute is reached
87+
final long currentTime = System.currentTimeMillis();
88+
// the cursor points to the latest request time and the next position is the oldest one
89+
final int oldestRequestTimeCursor = (requestTimesCursor + 1) % 12;
90+
final long oldestRequestTime = requestTimes[oldestRequestTimeCursor];
91+
if (oldestRequestTime + 60_000 >= currentTime) {
92+
try {
93+
// sleep at least until the oldest request is 60s old, but not more than 60s
94+
final int minSleepTime = (int) (currentTime - oldestRequestTime);
95+
Thread.sleep(minSleepTime + throttleRandom.nextInt(60_000 - minSleepTime));
96+
} catch (InterruptedException e) {
97+
System.err.println("Error while throttling the RecordingDownloader.");
98+
e.printStackTrace();
99+
}
100+
}
101+
requestTimesCursor = oldestRequestTimeCursor; // the oldest value needs to be overridden
102+
requestTimes[requestTimesCursor] = System.currentTimeMillis();
103+
104+
// Handle ReCaptchaExceptions by retrying the request once after a while
105+
try {
106+
return executeRequest(request);
107+
} catch (ReCaptchaException e) {
108+
try {
109+
System.out.println("Throttling the RecordingDownloader to handle a ReCaptchaException. Sleeping for 35-60 seconds.");
110+
Thread.sleep(35_000 + throttleRandom.nextInt(25_000));
111+
} catch (InterruptedException ie) {
112+
System.err.println("Error while throttling the RecordingDownloader "
113+
+ "to handle a ReCaptchaException");
114+
ie.printStackTrace();
115+
e.printStackTrace();
116+
}
117+
return executeRequest(request);
118+
}
119+
}
120+
121+
@Nonnull
122+
private Response executeRequest(@Nonnull final Request request) throws IOException,
123+
ReCaptchaException {
72124
final Downloader downloader = DownloaderTestImpl.getInstance();
73125
Response response = downloader.execute(request);
74126
String cleanedResponseBody = response.responseBody().replaceAll(IP_V4_PATTERN, "127.0.0.1");

0 commit comments

Comments
 (0)