Skip to content

Commit d635d4d

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 d635d4d

1 file changed

Lines changed: 60 additions & 3 deletions

File tree

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

Lines changed: 60 additions & 3 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

@@ -37,17 +38,31 @@
3738
*/
3839
class RecordingDownloader extends Downloader {
3940

40-
public final static String FILE_NAME_PREFIX = "generated_mock_";
41+
public static final String FILE_NAME_PREFIX = "generated_mock_";
4142

4243
// From https://stackoverflow.com/a/15875500/13516981
43-
private final static String IP_V4_PATTERN =
44+
private static final String IP_V4_PATTERN =
4445
"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
4546

4647
private int index = 0;
4748
private final String path;
4849

50+
// try to prevent ReCaptchaExceptions / rate limits by tracking and throttling the requests
4951
/**
50-
* Creates the folder described by {@code stringPath} if it does not exists.
52+
* The maximum number of requests per 20 seconds which are executed
53+
* by the {@link RecordingDownloader}.
54+
* 20 seconds is used as upper bound because the rate limit can be triggered within 30 seconds
55+
* and hitting the rate limit should be prevented because it comes with a bigger delay.
56+
* The values can be adjusted when executing the downloader and running into problems.
57+
* <p>TODO: Allow adjusting the value by setting a param in the gradle command</p>
58+
*/
59+
private static final int MAX_REQUESTS_PER_20_SECONDS = 30;
60+
private static final long[] requestTimes = new long[MAX_REQUESTS_PER_20_SECONDS];
61+
private static int requestTimesCursor = -1;
62+
private static final Random throttleRandom = new Random();
63+
64+
/**
65+
* Creates the folder described by {@code stringPath} if it does not exist.
5166
* Deletes existing files starting with {@link RecordingDownloader#FILE_NAME_PREFIX}.
5267
* @param stringPath Path to the folder where the json files will be saved to.
5368
*/
@@ -69,6 +84,48 @@ public RecordingDownloader(final String stringPath) throws IOException {
6984
@Override
7085
public Response execute(@Nonnull final Request request) throws IOException,
7186
ReCaptchaException {
87+
88+
// Delay the execution if the max number of requests per minute is reached
89+
final long currentTime = System.currentTimeMillis();
90+
// the cursor points to the latest request time and the next position is the oldest one
91+
final int oldestRequestTimeCursor = (requestTimesCursor + 1) % requestTimes.length;
92+
final long oldestRequestTime = requestTimes[oldestRequestTimeCursor];
93+
if (oldestRequestTime + 20_000 >= currentTime) {
94+
try {
95+
// sleep at least until the oldest request is 20s old, but not more than 20s
96+
final int minSleepTime = (int) (currentTime - oldestRequestTime);
97+
Thread.sleep(minSleepTime + throttleRandom.nextInt(20_000 - minSleepTime));
98+
} catch (InterruptedException e) {
99+
// handle the exception gracefully because it's not critical for the test
100+
System.err.println("Error while throttling the RecordingDownloader.");
101+
e.printStackTrace();
102+
}
103+
}
104+
requestTimesCursor = oldestRequestTimeCursor; // the oldest value needs to be overridden
105+
requestTimes[requestTimesCursor] = System.currentTimeMillis();
106+
107+
// Handle ReCaptchaExceptions by retrying the request once after a while
108+
try {
109+
return executeRequest(request);
110+
} catch (ReCaptchaException e) {
111+
try {
112+
// sleep for 35-60 seconds to circumvent the rate limit
113+
System.out.println("Throttling the RecordingDownloader to handle a ReCaptcha."
114+
+ " Sleeping for 35-60 seconds.");
115+
Thread.sleep(35_000 + throttleRandom.nextInt(25_000));
116+
} catch (InterruptedException ie) {
117+
// handle the exception gracefully because it's not critical for the test
118+
System.err.println("Error while throttling the RecordingDownloader.");
119+
ie.printStackTrace();
120+
e.printStackTrace();
121+
}
122+
return executeRequest(request);
123+
}
124+
}
125+
126+
@Nonnull
127+
private Response executeRequest(@Nonnull final Request request) throws IOException,
128+
ReCaptchaException {
72129
final Downloader downloader = DownloaderTestImpl.getInstance();
73130
Response response = downloader.execute(request);
74131
String cleanedResponseBody = response.responseBody().replaceAll(IP_V4_PATTERN, "127.0.0.1");

0 commit comments

Comments
 (0)