[throttled-restart.tcl] throttling 적용하여 파일변경시 자동으로 프로세스 재시작하기와 Tcl 취향고백

🗓️ 24 Sep, 2025

코딩을 하다보면, 파일변경시에 자동으로 빌드, 서버재시작, 혹은 테스트케이스를 실행 되도록 하면 꽤 편리할 수 있다.

커먼리습 같은 경우엔 바로 실행중인 코드가 변경되는 방식이어서 신경 쓸게 별로 없고, Rails 같은 웹프레임웍은 자동으로 재시작하거나 변경된 코드를 다시 로딩해주기도 하고, PHP 계열도 웹브라우저를 refresh하면 자동적으로 새 코드으로 실행하게 된다.

다른 컴파일이 필요한 언어나, 파이썬, 펄 같은 언어들, 혹은 자동으로 코드에 반영되는 언어/프레임웍을 사용하더라도, 원하는 태스크(예: 테스트케이스 실행) 같은걸 걸어 놓으려면 커맨드라인에서 지정해두는게 낫다.

문제 #1: 파일변경을 모니터링하기

  1. 대부분의 언어/프레임웍들은 이런 "파일변경 모니터링" 기능이 없다.

    • ==> 다른 외부도구를 커맨드라인에서 연결해서 사용하면 될 것 같다.

시도 #1: fswatch으로 파일변경을 모니터링하고 지정한 커맨드 실행하기

  1. fswatch 커맨드라인 도구를 사용하기로 했다. (freebsd, linux 모두 지원)

일단 간단하게 시작: 모든 변경을 STDIN으로 전달 받음

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  # fswatch: ~/w 디렉토리 이하의 모든 변경을 모니터링.
  #     -L (follow-symlink)
  #     -r (recurse-subdir)
  #
  # perl: STDIN 입력을 모두 그대로 출력. (echo)
  $ fswatch -Lr ~/w | perl -ne 'print'

  # STDOUT출력이 있을 때마다 make 실행되도록 xargs 연결:
  $ fswatch -Lr ~/w | perl -ne 'print' | xargs -n1 -IXXX make
  # xargs -n1 -IXXX :
  #     .. STDIN의 첫번째 줄만 전달 (-n1)
  #     .. 커맨드라인(make) 부분에서 XXX 부분을 STDIN 문자열으로 대체 (-IXXX)

시도 #2: throttling, Perl One-liner

  1. (문제) 한 변경에 너무 많은 triggering 갯수

    1. $HOME/w 디렉토리에 한 번에 여러 파일이 생성/삭제/수정되면 그게 한 라인씩 찍힘.
    2. 한 뭉치 변경은 하나의 라인으로 뭉치면 좋겠다.
    3. 한 단위시간 동안 이미 트리거 되었다면, 단위시간내에 다시 발생한 이벤트는 트리거하지 않도록 무시하자.
    4. ==> throttling하자
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  $ fswatch -Lr ~/w | perl -ne '
      BEGIN{
          $|=1;       # autoflush(STDOUT)
          $SECS=5;
          $last=time;
      } {
          my $cur=time;
          if($last < ($cur - $SECS)){
              print $_;
              $last=$cur;
      }
  }'
  1. 조금 문제: 시작하자마, $SECS-이내에 발생한 이벤트는 무시된다.
  2. 커맨드 실행은, 위의 xargs 부분 파이프연결을 더하면 된다.

문제 #2: pid-file, kill-switch이 필요해졌다

트리거링 이벤트/입력에 대해 스로틀링하고, 원하는 커맨드를 실행하는 것도 했지만, "재시작"을 위해서는 재시작 대상 프로세스가 좀 더 지원해줄 필요가 있었음.

  1. pid-file : 재시작을 위해 기존 프로세스 id (pid)을 파일에 기록해 두서 그걸 kill해야 하거나,
  2. kill-switch : (or) 재시작 대상이 되는 프로세스가 별도의 unix named pipe / socket file을 열고, 그 입력에 따라 스스로 gracefully shutdown 하는 지원이 필요.

기존 프로세스를 종료하려면 어떤 프로세스인지 알아야 할테니까. (단순하게 pkill 등으로 프로세스 이름을 적당히 찍어서 그렇게 해도 되긴 하겠지만)

시도 #3: 그래서 짜버렸다 throttled-restart.tcl

https://github.com/ageldama/throttling-restart.tcl

tcl 8.6, tcllib 이용해서, 원하는 커맨드를 시작/재시작하고 자동으로 STDIN에 따라 종료/재시작하는 작은 유틸리티를 작성해봤다.

자동적으로 프로세스를 시작하고, pid을 관리/종료도 자동으로 해주니 재시작 대상 프로그램은 변경/확장될 필요가 전혀 없다.

후기: tcl

지금 생각해보니, subprocess을 꼭 분리된 thread 안에서 관리할 필요는 없었을거 같기도 하다. 처음 설계할 때엔 blocked 상태에 빠질까봐 분리를 했었지만.

tcl을 사용해서 뭔가를 진지하게 짜기 시작하니까 재밌었다:

  1. [ GOOD ] event-loop, evented i/o이 기본이어서 좋았다.

    • node.js, python의 asyncio 등처럼 콜백지옥도 별로 없고, 또 async/await 같은 function coloring 문제도 없었다.
    • 적당히 golang의 i/o system처럼 비동기이지만 적당히 동기형처럼 짤 수 있어서 좋았다.
    • 분명히 callback을 지정해서 이벤트를 처리하는데도 그렇게 콜백지옥이 되지 않아서 신기할 정도.
    • 특히 tcl 입출력 모델은 정말 깔끔하고 편리한 것 같다.
  2. [ GOOD ] 스레딩도 신선했다.

    • Python, Ruby 등이 모두 GIL문제1 우회하려 애써야 하거나 했던데 비해서,
    • 단순하게 그냥 Tcl/C API 수준에서 스레드를 만들고 분리된 Tcl Interpreter을 만들어 서로 연결되도록 하는 방식이어서, 문제가 생길 여지가 훨씬 적었다.
  3. Perl으로 짰었다면,

    • perl / thread으로 좀 고민 했을거 같고, (어차피 결국 안썼겠지만)
    • AnyEvent 모듈을 적절히 썼을거 같다.
  4. [ BAD ] 여전히 Tcl 문법, expansion등에 대해서는 가끔 생소해진다.

    • 현대에 익숙한 언어들(펄, C/C++, Go, Java, Python) 등와는 많이 다르기도 하고.
    • 의외로 Tcl의 간단한 규칙2에서 파생되는 복잡해지는 상황이나,
    • 내장된 String interpolation / Evaluation 규칙 때문에, Escaping이 필요한데, 그럴 방법이 잘 떠오르지 않을 때도 있었던거 같다.
  5. [ BAD ] Closure이 바로 지원되지 않아서 거 좀…

  6. [ BEST ] 그런데 위 (4), (5)은 Tcl이라 생기는 이슈이고, 사실 그런 이슈 때문에 String을 기반으로 한 리습언어 같다, 거기에 바로 현재 쓸 수 있는, 그리고 리눅스/유닉스 시스템에 잘 녹아있는 것이라 좋은거 같다.

Footnotes


1

파이썬은 3.14 이후로 GIL을 끈 방식으로 실행할 수 있도록 했지만, 사용해보진 않아서.

2

https://learnxinyminutes.com/tcl/ 간단한 기본 문법규칙. 정말 다른 언어에 비해서 단순하긴 하고.