Настройка блокирующего файла для чтения в Java

Я хотел бы настроить файл блокировки для чтения на Java. То есть иметь такой файл, что при обертывании FileInputStream и вызове любого метода read() вызов блокируется.

Я не могу придумать простой независимый от ОС способ - в Unix-подобных ОС я мог бы попытаться создать FIFO, используя mkfifo, и прочитать из этого файла. Возможным обходным решением было бы просто создать очень большой файл и прочитать его - чтение вряд ли завершится до того, как я зафиксирую стек, но это уродливо и медленно (и действительно чтение может быть невероятно быстрым при кэшировании).

Соответствующий случай сокета read() настроить тривиально — создайте сокет сами и прочитайте из него, и вы можете иметь детерминированную блокировку.

Цель состоит в том, чтобы изучить стек метода, чтобы определить, каковы верхние кадры в таком случае. Представьте, что у меня есть компонент, который периодически выбирает трассировку стеков всех запущенных потоков, а затем пытается классифицировать, что этот поток делает в данный момент. Одна вещь, которую он может делать, это файловый ввод-вывод. Поэтому мне нужно знать, как выглядит «верхняя часть стека» во время файлового ввода-вывода. Я уже определил это экспериментальным путем (просто прочитайте файл различными способами и попробуйте стек), но я хочу написать тест, который потерпит неудачу, если это когда-либо изменится.

Естественный способ написать такой тест — запустить поток, который читает файл, а затем проверить верхние кадры. Чтобы сделать это надежно, мне нужно блокирующее чтение (иначе поток может завершить чтение до того, как будет взята трассировка стека и т. д.).


person BeeOnRope    schedule 20.01.2015    source источник
comment
Не могли бы вы объяснить блокировку? Должен ли он блокироваться для любого Java-приложения или только для одного экземпляра Вашего приложения?   -  person maslan    schedule 21.01.2015
comment
Я обновлю основной вопрос с деталями.   -  person BeeOnRope    schedule 21.01.2015
comment
Я не могу придумать, как сделать то, что вы хотите, в Windows (я думаю, это ваша проблема с независимостью от ОС). Судя по вашему описанию, я не думаю, что захват FileInputStream.getChannel() и постоянный сброс курсора будут работать, поскольку это на некоторое время изменит трассировку стека. Если вы думаете, что это возможно, я могу расширить ответ. Точно так же я предполагаю, что расширение FileInputStream не будет работать, поскольку это методы FileInputStream.read *, которые вы пытаетесь снять, если я правильно понимаю.   -  person J Richard Snape    schedule 28.01.2015
comment
У вас есть возможность использовать свой собственный FileInputStream? Затем вы можете получить стек и проверить его до того, как файл будет действительно прочитан. Это также звучит так, как будто вы хотите предотвратить доступ к файлам в нежелательном контексте. Обычно это работа SecurityManager.   -  person M.P. Korstanje    schedule 30.01.2015
comment
Я не хочу предотвращать доступ к файлам в нежелательном контексте. Я хочу иметь возможность выполнять дамп (внешний) работающего потока и определять, что он находится в файловом вводе-выводе. Конечно, я мог бы использовать свой собственный FIS, но проблема в том, что я не могу получить дамп из потока, выполняющего чтение. По определению, когда метод native read0 (или любой другой) находится на вершине стека, я не контролирую ситуацию и не могу сбросить этот поток. Конечно, я мог бы сделать это и раньше, но это дает мне правильный верхний кадр или два.   -  person BeeOnRope    schedule 31.01.2015
comment
Ах. Я понимаю, чего ты хочешь сейчас. В таком случае. Нет. В любом случае не ОС независимо.   -  person M.P. Korstanje    schedule 02.02.2015


Ответы (6)


Чтобы получить гарантированно заблокированный ввод-вывод, прочитайте с консоли, например. /dev/console в Linux или CON в Windows.

Чтобы сделать эту платформу независимой, вы можете взломать FileDescriptor из FileInputStream:

    // Open a dummy FileInputStream
    File f = File.createTempFile("dummy", ".tmp");
    f.deleteOnExit();
    FileInputStream fis = new FileInputStream(f);

    // Replace FileInputStream's descriptor with stdin
    Field fd = FileInputStream.class.getDeclaredField("fd");
    fd.setAccessible(true);
    fd.set(fis, FileDescriptor.in);

    System.out.println("Reading...");
    fis.read();
    System.out.println("Complete");

ОБНОВЛЕНИЕ

Я понял, что вам даже не нужен метод для блокировки. Чтобы просто получить правильную трассировку стека, вы можете вызвать read() для недопустимого FileInputStream:

    FileInputStream fis = new FileInputStream(new FileDescriptor());
    fis.read(); // This will throw IOException exactly with the right stacktrace

Если вам все еще нужен блокирующий read(), именованные каналы — это то, что вам нужно: запустите mkfifo с помощью Runtime.exec в системах POSIX или создайте \\.\PIPE\MyPipeName в Windows.

person apangin    schedule 29.01.2015
comment
Это также пришло мне в голову (хотя это не трюк, чтобы рефлексивно поменять местами fd), но есть ли какой-либо другой блокирующий поток, который можно использовать, кроме стандартного ввода? Проблема в том, что на стандартный ввод могут быть какие-то входные данные, и я не должен их использовать. - person BeeOnRope; 29.01.2015
comment
Идея использовать неверный filedesc очень хороша, спасибо! Вручая тебе награду... - person BeeOnRope; 02.02.2015

В любом случае я не знаю, как сделать файл независимым от ОС способом, который всегда будет блокироваться при чтении.

Если бы я пытался найти трассировку стека при вызове определенной функции, я бы запускал программу под отладчиком и устанавливал точку останова на этой функции. Тем не менее, точки останова метода замедлят вашу программу и дадут вам результаты, отличные от тех, которые вы обычно получаете, если важно время.

Если у вас есть доступ к исходному коду программы, вы можете создать поддельный FileInputStream, который расширяет реальный, но всегда блокируется при чтении. Все, что вам нужно сделать, это отключить операторы импорта во всем коде. Однако это не захватит места, где вы не можете отключить операторы импорта, и это может быть проблемой, если есть много кода.

Если вы хотите использовать свой собственный FileInputStream без изменения исходного кода программы или компиляции, вы можете создать собственный загрузчик классов, который загружает ваш собственный класс FileInputStream вместо реального. Вы можете указать, какой загрузчик классов использовать в командной строке:

java -Djava.system.class.loader=com.test.MyClassLoader xxx

Теперь, когда я об этом подумал, у меня появилась идея получше: вместо того, чтобы создавать собственный поток FileInputStream, который блокируется в read(), создайте собственный поток FileInputStream, который выводит трассировку стека в read(). Затем пользовательский класс может вызвать реальную версию read(). Таким образом вы получите все трассировки стека для всех вызовов.

person OfNothing    schedule 20.01.2015
comment
Дело в том, что я хочу увидеть, что такое трассировка стека, когда происходит чтение. Я не могу установить точку останова, потому что я точно не знаю, что это за метод (и, вероятно, это нативный метод, на который трудно поставить точку останова). Мне это нужно для автоматического теста, который проверяет, что верхние кадры соответствуют ожиданиям при выполнении файлового ввода-вывода. - person BeeOnRope; 21.01.2015
comment
@BeeOnRope Итак, вы хотите, чтобы чтение зависло, тогда вы собираетесь отправить сигнал JVM, чтобы сбросить трассировку стека, чтобы убедиться, что все правильно? Можно поставить точки останова Java на методы, встроенные в JVM. Здесь, я думаю, вы хотите FileInputStream.read(). Я здесь смотрю исходный код для java7. FileInputStream.read() вызывает собственную функцию: read0() Поскольку это звучит так, как будто вы проводите тестирование, вы можете просто реализовать его в зависимости от ОС, например, на вашей тестовой платформе Nix, создайте fifo , на вашей платформе Windows сделайте ... *кашель что-нибудь. - person OfNothing; 21.01.2015
comment
Да, хотя я не использую сигнал, а просто что-то вроде ThreadMXBean.getThreadInfo() может вернуть стек для любого потока. Это для автоматизированного тестирования, поэтому точки останова не применяются. Другая проблема заключается в том, что я пытаюсь проверить, что метод на самом деле FileInputStream.read(). - person BeeOnRope; 21.01.2015

Насколько я понимаю, вы хотите написать тест, который проверяет трассировку стека метода FileInputStream.read(). Что насчет потомков FileInputStream, если они переопределяют метод read()?

Если вам не нужно проверять потомков, я думаю, вы можете использовать Интерфейс JVM Tool, вставив точку останова во время выполнения в нужном методе, а в событии обработки этого события (точки останова) - дамп трассировки стека. После завершения дампа вы удаляете точку останова и продолжаете выполнение. (Все это происходит во время выполнения с использованием этого API, никакой черной магии :))

person Genry    schedule 29.01.2015

У вас может быть отдельный поток, отслеживающий изменения в файле время доступа и создать дамп потока jvm, когда это произойдет. Что касается создания дампа потока в коде, который я не пробовал, но похоже, что здесь дан ответ: Создать дамп потока Java без перезапуска.

Я не знаю, насколько хорошо это будет работать с синхронизацией между вашими потоками, но я думаю, что это должно быть довольно близко. Я также не на 100% уверен в независимости этого решения от ОС, поскольку я его не тестировал, но оно должно работать для большинства современных систем. См. javadocs в java.nio.file.attribute.BasicFileAttributes, чтобы узнать, что будет возвращено, если оно не поддерживается.

person Foosh    schedule 27.01.2015
comment
Я думаю, что есть некоторое недоразумение - я хочу в тестовом сценарии настроить чтение детерминированной блокировки, чтобы я мог сделать дамп этого потока и проверить, что ожидаемые кадры находятся на вершине стека. Сбросить сам стек легко, например, с помощью ThreadMXBean.getThreadInfo(). - person BeeOnRope; 28.01.2015
comment
Я понял, что, я пытался ответить, как получить тот же результат. Время доступа к файлу будет обновлено, когда начнется чтение, поэтому, если вы сгенерируете свой дамп в этот момент, вы получите верхнюю часть стека потоков по желанию в коде, чтобы вы могли работать с ним в своем модульном тесте по мере необходимости. Нет необходимости блокировать, если вы по-прежнему получаете нужные данные. Есть способы заблокировать, но это грязно и требует собственных взаимодействий и использования поддельного FileInputStream, упомянутого @OfNothing. - person Foosh; 28.01.2015
comment
Я не вижу причин полагать, что если я сделаю дамп в момент изменения метки времени, я получу вызовы ввода-вывода в верхней части стека. Я бы предпочел предположить, что ввод-вывод выполняется в этот момент. - person BeeOnRope; 28.01.2015
comment
Время доступа обновляется при открытии файла для чтения, а не при завершении операции ввода-вывода, как в случае создания и изменения. Это достаточно просто проверить эмпирически с помощью команды tail -f в системе *nix. - person Foosh; 28.01.2015

Один трюк: если можно изменить ваш API, чтобы он возвращал Reader вместо File, то вы можете обернуть строку с помощью пользовательского StringReader (скажем, class SlowAsRubyStringReader extends Reader), который переопределяет различные методы int read() с помощью Thread.sleep(500), прежде чем он сделает реальный работай. Только во время тестирования, конечно.

@см. http://docs.oracle.com/javase/7/docs/api/java/io/StringReader.html

Я думаю, что здесь есть более серьезная проблема, а не только файлы: вы хотите проверить контекст, в котором API вызывается во время ваших тестовых случаев, не так ли? То есть вы хотите иметь возможность изучить стек и сказать: «Ага! Я поймал вас на вызове MudFactory API из объекта JustTookABath, НЕВЕРОЯТНО!». Если это так, то вам, возможно, придется углубиться в динамические прокси, которые позволят вам перехватывать вызовы функций, или использовать аспектно-ориентированное программирование, которое позволяет вам делать то же самое, но более систематически. См. http://en.wikipedia.org/wiki/Pointcut.

person vijucat    schedule 01.02.2015

read() быстро погружается в нативный код, поэтому да, вероятно, нужно перейти на нативный код, чтобы заблокировать на этом уровне. В качестве альтернативы вы можете захотеть зарегистрировать трассировку стека в точке вашего кода до или после read().

Что-то типа:

log ( ExceptionUtils.getStackTrace(new Exception()) );

Документация по ExceptionUtils находится здесь: https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/exception/ExceptionUtils.html

person David Soroko    schedule 21.01.2015
comment
Регистрация трассировки до или после не имеет значения - мне нужно получить кадры, которые будут на вершине стека, когда чтение действительно происходит (т. Е. Верхний кадр вызывает собственный метод чтения). Я не понимаю, почему мне обязательно нужно перейти на родной язык, чтобы настроить блокирующее чтение. Конечно, сокеты легко настроить в Java, хотя в конце цепочки, конечно, есть нативные методы. - person BeeOnRope; 21.01.2015
comment
ExceptionUtils также дает вам стековые кадры. Глядя на источник, вы можете завершить картину. - person David Soroko; 21.01.2015
comment
Я очень хорошо знаю, как получить трассировку стека текущего потока. Что мне нужно, так это то, что какой-то поток, назовем его T1, перешел в блокирующее чтение, после чего я в другом потоке T2 получу трассировку стека для T1, чтобы увидеть, какие кадры находятся на вершине стека, когда происходит чтение . Я не могу сделать это из T1, который по определению (а) заблокирован и (б) в нативном коде, который я не могу изменить. - person BeeOnRope; 21.01.2015
comment
T1 может поместить захваченные кадры в какой-то буфер и позволить T2 проверять данные, когда они приходят. - person David Soroko; 21.01.2015
comment
Это невозможно, потому что T1 заблокирован в нативном методе. По определению я не могу попросить его взять трассировку стека в этот момент... - person BeeOnRope; 21.01.2015
comment
Конечно, я предполагал, что T1 буферизует данные стека непосредственно перед входом в read(). - person David Soroko; 21.01.2015
comment
... но это разрушило бы всю цель вопроса. В любой момент я могу написать код Java (и в этот момент я могу буферизовать стек), я точно знаю, каким будет стек. Что мне нужно знать о стеке, включая нативный метод чтения, который происходит после того, как мы оставляем Java-код, который я могу написать. - person BeeOnRope; 21.01.2015