среда, 26 октября 2011 г.

Запуск большого количества параллельных задач в надежных песочницах

Недавно возникла одна задача, удовлетворяющего меня на 100% ответа на которую я пока не смог найти (вариантов разных придумалась масса).

Предлагаю порассуждать на тему возможных решений.

Итак, есть крупное Enterprise-решение, написанное на Java. Как часть своей функциональности, оно предлагает пользователям возможность создавать собственные скрипты, написанные на некотором языке, которые запускаются на выполнение в песочницах, и возвращают некоторый результат. По замыслу, это именно что чистые функции — принимают значения, производят вычисления над ними, возвращают результат, все. Никаких обращений к внешним ресурсам, никаких side effects. Скриптов этим может запускаться на выполнение много — сотни в минуту, примерно так. Возникает вопрос, как организовать надежную песочницу для них.



Предположим, мы будем использовать в качестве языка для этих скриптов Jython/Groovy/JRuby, какой угодно скриптовый язык под JVM, так как остальная часть системы написана на Java.

Сразу проблема — штатные средства JVM/архитектура Windows/Unix не позволяют организовать песочницу в рамках родительского процесса, в котором выполняется основная программа. Ограничение доступа к частям JDK по списку разрешенных пакетом, система пермишенов на доступ к сети, файлам, и все прочее хорошо, но одно но — нет никакого нормального способа запретить скрипту выполнить new int[10000000000000] и свалить JVM с OutOfMemory, тупо потому, что весь heap расшаривается между всеми потоками процесса, и ничего с этим поделать нельзя (строго говоря, единственный теоритический способ, это перехватывать все операции выделения памяти с помощью jvm agent, и не запускать на выполнение байткоды, запрашивающие выделение памяти через new, без проверки того, сколько памяти запрашивается… это потребует написание агента, который модифицирует байткод всех загружаемых классов, и вставляет нужные проверки вокруг всех операций выделения памяти, в том числе внутри самих классов JDK… мрак).

Т.е. для каждого скрипта нам нужен отдельный процесс. Но раз так, то JVM нам сразу для этого мало подходит. Потому что она изначально не предназначена для выполнения одноразовых скриптов, она имеет большой оверхед по запуску, инициализации рантайма (даже для client jvm), она потребляет много памяти на каждый свой процесс (в том числе потому, что на каждый процесс требуется создавать свой perm gen, в который грузятся все классы… и даже class data sharing вряд ли радикально сократит потребление памяти, хотя тут я еще собираюсь поэкспериментировать).

Следующий вариант — использовать для этих скриптов что-то вроде питона, и запускать его нативные процессы (под юниксом, через форк), считая что так они будут создаваться намного быстрее… Интегрироваться с этими процессами из основной программы можно либо по TCP, либо через pipes.

Питон как язык удобен для того, чтобы пользователи писали скрипты именно на нем. Возникает вопрос, как обстоят дела с sandboxing у Питона, по аналогии с Java Security Manager.

Следующий вариант — использовать язык типа Erlang, в котором порождение процессов гораздо проще, но и сам язык гораздо более экзотический…