У меня есть класс, который слабо реализует протокол HTTP Ranged-GET .По сути, некоторый код вызывает этот класс, говоря ему загрузить файл.Ниже MyClass
отвечает за загрузку файла в чанках четного размера (до последнего чанка, который может быть переменной длины) и отправку его в другую службу.При вызове он устанавливает размер файла на основе длины экземпляра Content-Range из первого HTTP-ответа.
Класс использует ExecutorService с размером пула потоков 1 для управления потоками.
Ниже приведена соответствующая реализация, с некоторыми ручными помахиванием над функциями, обрабатывающимиGETs и PUT.
class MyClass implements Runnable {
private long start;
private long chunkSize;
private int chunkNumber;
private int fileSize = 0;
private static final int MAX_RETRIES = 3;
public static final ExecutorService ES = Executors.newSingleThreadExecutor();
public MyClass(long start, long chunkSize, int chunkNumber) {
this.start = start;
this.chunkSize = chunkSize;
this.chunkNumber = chunkNumber;
}
public void run() {
for (int i = 0; i < MAX_RETRIES; i++) {
long end = start + chunkSize - 1; // inclusive so subtract 1
// doHttpGet() is a private instance function...
// if fileSize == 0 (i.e. first chunk downloaded), this will set the fileSize
doHttpGet(start, end);
// doHttpPost() is a private instance function
// it builds the POST from the GET message, which I'm not bothering to show here
if (!doHttpPost()) {
continue;
} else {
submitNextChunk(this);
break;
}
}
}
// this is the function a client uses to invoke the class
public static void submitWork(long newStartByte, long chunkSize, int chunkNumber) {
MyClass mc = new MyClass(newStartByte, chunkSize, chunkNumber);
if (ES.submit(mc) == null) {
//log error
}
}
// PROBLEM AREA?!?!
private static void submitNextChunk(MyClass mc) {
mc.chunkNumber++;
mc.start += mc.chunkSize;
// LOGGER.debug("start=" + mc.start + "\n" + "fileSize=" + mc.fileSize)
if (mc.start < mc.fileSize) {
if (ES.submit(mc) == null) {
//log error
}
}
}
}
А вот фрагмент кода, который вызывает MyClass
.
long chunkSize = //somecalculation
DownloadAction.submitWork(0L, chunkSize, 1));
Этот код работал хорошо, в течение длительного времени.Однако сейчас я замечаю потенциально недетерминированное поведение, когда файл для загрузки очень мал (например, <50 байт).Кажется, что происходит то, что функция <code>submitNextChunk(), похоже, неправильно оценивает mc.start < mc.fileSize
.Например, если мы установим packetSize = 100K и используем 50-байтовый файл, то, что я вижу - через Wireshark - это непрерывные HTTP-запросы GET, запрашивающие байты 0-99999, 100000-199000 и 200000-299000,.... и т. д. (Код на другом конце также немного поврежден, поскольку он продолжает давать нам исходные 50 байтов, а не код ошибки HTTP вне допустимого диапазона ... но это другая история.)
Меня беспокоило, что есть тонкое состояние гонки:
Если я добавлю логин submitNextChunk()
, чтобы распечатать start
и fileSize
, то я только см. один лог-оператор start = 100000 и fileSize = 100, и функция правильно оценивает выражение меньше, чем false.Это имеет смысл, поскольку это будет первый и единственный раз, когда будет вызван submitNextChunk()
, и, поскольку это выражение оценивает false, функция прерывается.
Я обеспокоен тем, что это как-то связано с многопоточностью, поскольку без операторов отладки, это выражение меньше чем явно приводит к true
, чего не должно быть.