<< отредактировано из-за комментариев >>
Как упомянуто в одном из комментариев, отделение синтаксического анализа (что является медленным) от выполнения (которое является быстрым) является обязательным, если вам нужна производительность.
Для реактивной перезагрузки исходного кода скрипта мы можем, например, использовать java nio watch service :
import groovy.lang.*
import java.nio.file.*
def source = new File('script.groovy')
def shell = new GroovyShell()
def script = shell.parse(source.text)
def watchService = FileSystems.default.newWatchService()
source.canonicalFile.parentFile.toPath().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
boolean keepWatching = true
Thread.start {
while (keepWatching) {
def key = watchService.take()
if (key.pollEvents()?.any { it.context() == source.toPath() }) {
script = shell.parse(source.text)
println "source reloaded..."
}
key.reset()
}
}
def binding = new Binding()
def start = System.currentTimeMillis()
for (i=0; i<100; i++) {
script.setBinding(binding)
def result = script.run()
println "script ran: $result"
Thread.sleep(500)
}
def delta = System.currentTimeMillis() - start
println "took ${delta}ms"
keepWatching = false
Приведенное выше запускает отдельный поток наблюдателя, который использует службу наблюдения Java для мониторинга родительского каталога на предмет изменений файлов и перезагружает источник сценария при обнаружении изменения. Это предполагает Java версии 7 или более поздней. Спящий режим предназначен для того, чтобы было легче поиграться с кодом, и его, естественно, следует убрать при измерении производительности.
Сохранение строки System.currentTimeMillis()
в script.groovy
и выполнение приведенного выше кода приведет к его цикличному выполнению дважды в секунду. Внесение изменений в script.groovy
во время цикла приводит к:
~> groovy solution.groovy
script ran: 1557302307784
script ran: 1557302308316
script ran: 1557302308816
script ran: 1557302309317
script ran: 1557302309817
source reloaded...
script ran: 1557302310318
script ran: 1557302310819
script ran: 1557302310819
source reloaded...
где строки source reloaded...
печатаются всякий раз, когда в исходный файл вносятся изменения.
Я не уверен насчет окон, но я полагаю, по крайней мере, в Linux, что java использует систему fsnotify под прикрытием, которая должна сделать часть мониторинга файлов работоспособной.
Следует отметить, что если нам действительно не повезет, переменная сценария будет сброшена потоком наблюдателя между двумя строками:
script.setBinding(binding)
def result = script.run()
, который нарушит код, поскольку перезагруженный экземпляр скрипта не будет иметь привязки Чтобы исправить это, мы можем, например, использовать блокировку:
import groovy.lang.*
import java.nio.file.*
import java.util.concurrent.locks.ReentrantLock
def source = new File('script.groovy')
def shell = new GroovyShell()
def script = shell.parse(source.text)
def watchService = FileSystems.default.newWatchService()
source.canonicalFile.parentFile.toPath().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
lock = new ReentrantLock()
boolean keepWatching = true
Thread.start {
while (keepWatching) {
def key = watchService.take()
if (key.pollEvents()?.any { it.context() == source.toPath() }) {
withLock {
script = shell.parse(source.text)
}
println "source reloaded..."
}
key.reset()
}
}
def binding = new Binding()
def start = System.currentTimeMillis()
for (i=0; i<100; i++) {
withLock {
script.setBinding(binding)
def result = script.run()
println "script ran: $result"
}
Thread.sleep(500)
}
def delta = System.currentTimeMillis() - start
println "took ${delta}ms"
keepWatching = false
def withLock(Closure c) {
def result
lock.lock()
try {
result = c()
} finally {
lock.unlock()
}
result
}
, который несколько запутывает код, но защищает нас от проблем параллелизма, которые трудно отследить.