Состояние гонки через использование ExecutorService Java? - PullRequest
0 голосов
/ 19 сентября 2018

У меня есть класс, который слабо реализует протокол 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, чего не должно быть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...