Groovy скрипт перезагружается во время выполнения - PullRequest
0 голосов
/ 06 мая 2019

Я хочу иметь возможность выполнять отличный скрипт из моего Java-приложения. Я хочу перезагрузить Groovy скрипт во время выполнения при необходимости. Согласно их учебникам , я могу сделать что-то подобное:

long now = System.currentTimeMillis();
for(int i = 0; i < 100000; i++) {
    try {
        GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine("");                
        System.out.println(groovyScriptEngine.run("myScript.groovy", new Binding()););
    } catch (Exception e) {
        e.printStackTrace();
    }
}
long end = System.currentTimeMillis();

System.out.println("time " + (end - now));//24 secs

myScript.groovy

"Hello-World"

Это прекрасно работает, и скрипт перезагружается каждый раз, когда я меняю строку в myScript.groovy.

Проблема в том, что это неэффективно по времени, поэтому каждый раз он анализирует скрипт из файла.

Есть ли другая альтернатива? Например, что-то более умное, проверяющее, проанализирован ли уже сценарий и не изменился ли он с момента последнего анализа, не анализируйте его снова.

1 Ответ

1 голос
/ 08 мая 2019

<< отредактировано из-за комментариев >>

Как упомянуто в одном из комментариев, отделение синтаксического анализа (что является медленным) от выполнения (которое является быстрым) является обязательным, если вам нужна производительность.

Для реактивной перезагрузки исходного кода скрипта мы можем, например, использовать 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
}

, который несколько запутывает код, но защищает нас от проблем параллелизма, которые трудно отследить.

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