Let's start Scheme

2015-06-25

Platform specific issues

Recently I've faced to stability issues. The followings are the ones I've met:

Well, the one for Windows is kinda known for a while (except Windows Server one). These issues are categorised more or less platform specific issues.

The reason why I'm writing is because I have no idea why it happens and might be able to get some knowledge from comment (my googling ability is not high enought to find the solution). For example, the first one happens only on Arch Linux. The funny thing is that the reported bug didn't happen on my Arch Linux environment but different one mentioned on the comment. And never happened on other environment (e.g. Ubuntu or FreeBSD).

The second one happens only on my Linux CI environment. So I even can't debug. And, again, never happened on other Linux environment. (why?)

If you have any clue, please let me know.

2015-06-07

パラメータとスレッド(2)

前回の続き。

コードとか実装のアイデアというのは寝かせると浮かんでくるものではあるのだが、割と早めに浮かんできたので早速実装してみたという話。

Gaucheの動作はパラメータの初期値をグローバルに持っているから実現されている(それ以外にも手はあるのだが)という話から、とりあえず同様のことをしてみた。SagittariusではパラメータはSchemeで実装されているので実装ライブラリにWeakハッシュテーブルを持たせパラメータが参照される動的環境内に値がなければグローバルのテーブルから引っ張ってくるという実装。実装が簡単な上に効果は抜群で、前回のスクリプトはGaucheと同様の結果を返すようになった。さらに、Weakハッシュテーブルを使っているのでGCが走った際にどこからも参照されていなければ回収されるのでメモリの爆発もない。

この変更によるものではないと思うけど、プッシュしたらdrone.ioのテストがこけた・・・なんだろう?

2015-06-06

パラメータとスレッド

平行処理ライブラリの書き換えをしていてスレッドと動的環境(今回はパラメータ限定)に関連した挙動の違いが気になったのでメモ。

動作の調査としては、スレッドAで作成されたパラメータが親スレッド、スレッドAが生成される前から存在したスレッドB及びスレッドAの処理が終了後に生成されたスレッドCでどのように見えるかというもの。使用したのは以下のコード:
;; (param) library
(define-library (param)
  (export *param*)
  (import (scheme base))
  (begin
    (define *param* (make-parameter 10))))

;; script
(import (scheme base) (scheme load) (scheme write) (scheme eval) (srfi 18))

(define box (make-vector 1))
(define lock (make-mutex))
(define waiter (make-condition-variable))

(define t 
  (thread-start!
   (make-thread
    (lambda ()
      (mutex-unlock! lock waiter)
      ((vector-ref box 0))))))

(define (do-param v)
  (display (current-thread))
  (display (eval '(*param*) (environment '(param))))
  (eval `(*param* ,v) (environment '(param)))
  (display (eval '(*param*) (environment '(param))))
  (newline))

(thread-join! (thread-start! (make-thread
  (lambda () (do-param ":changed")))))

;; main thread
(do-param ":changed-main")

;; other thread?
(thread-join! (thread-start! (make-thread
  (lambda () (do-param ":changed-other")))))

(vector-set! box 0 (lambda () (do-param ":changed-before")))
(condition-variable-broadcast! waiter)
(thread-join! t)
調査はSRFI-18が使えるChibi、Gauche、Sagittariusでのみ。FomentやChickenもcond-expandを使って必要な部分だけ何とかすればいけるんだけど、面倒だったので今回はパス。悲しいことにR6RS処理系はSagittariusを除いてSRFI-18をサポートしてないのでそれらもパス。

結果は以下の通り
Chibi
#<Context -2373536>10:changed
#<Context -2555840>:changed:changed-main
#<Context -2108736>:changed-main:changed-other
#<Context -2412960>:changed-other:changed-before

Gauche
#<thread #f runnable 0x800a2a10>10:changed
#<thread "root" runnable 0x800a2e60>10:changed-main
#<thread #f runnable 0x800a28a0>:changed-main:changed-other
#<thread #f runnable 0x800a2b80>10:changed-before

Sagittarius
#<thread thread-9 runnable 0x80204320>10:changed
#<thread root runnable 0x8014ce10>#f:changed-main
#<thread thread-10 runnable 0x80204190>:changed-main:changed-other
#<thread thread-7 runnable 0x802044b0>#f:changed-before
Chibiは全てのスレッドでパラメータの共有しているっぽく、スレッドセーフではない模様。デフォルトでのビルドなのでビルドオプションによっては何かあるかもしれない。

GaucheはShiroさんから聞いていた通りスレッドをまたいでも初期値が保持される模様。

Sagittariusは別スレッドで作成されたパラメータの初期値は取れない、まぁ分かっていたことだけど。 親スレッドで作成されたものは子スレッドに渡るので必要なら親スレッドで準備すればよい話なのだが、これが面倒になってきたので何とかしたいなぁというのもある。

Shiroさんからの情報によると、Gaucheではパラメータの初期値はグローバルに持っていてごにょごにょやっているっぽい。ただ、局所的に作成されたパラメータはGCされないとのこと。(Weakハッシュテーブルとかでなんとかできないのだろうか?) 一応確認してみた。
(import (scheme base))

(do ((i 0 (+ i 1))) ((= i 100000)) (make-parameter 1))
;;(do ((i 0 (+ i 1))) ((= i 100000)) (cons 1 2))

;; trigger GC
(do ((i 0 (+ i 1))) ((= i 100)) (make-vector 100))
パラメータを作る場合と単なるコンスセルの場合でどれくらいメモリ割り当てが違うか。明示的には書いてないが、-f collect-statsオプションを渡すとGCの状況を教えてくれるのでそれを使用。っで以下が結果。
# parameter
% gosh -r7 -f collect-stats test.scm

;; Statistics (*: main thread only):
;;  GC: 14524416bytes heap, 1334816808bytes allocated
;;  stack overflow*: 0times, 0.00ms total/0.00ms avg

# cons
% gosh -r7 -f collect-stats test.scm

;; Statistics (*: main thread only):
;;  GC: 5943296bytes heap, 11413768bytes allocated
;;  stack overflow*: 0times, 0.00ms total/0.00ms avg
コンスセルとパラメータのサイズの違いは調べてないので分からないけど、GCが起きたと仮定するとパラメータとコンスセルの場合で同じメモリになるはずなので、パラメータは回収されていないようにみえる。

動作的にはGaucheの動作が望ましいので、何か手を加えようかな。ただメモリが爆発するのは嫌なので(これが嫌でシンボルもGC対象にしたわけだし)、どうにもならなさそうなら現状維持の方向で。

2015-06-05

getaddrinfo on Cygwin

I think I found a bug on Cygwin or could already be known issue but it kinda took my half a day so might be good to share.

The problem is related to getaddrinfo as this article's title says. The story starts from that I've re-written the executor on (util concurrent), then for some reason getting the error which says 'Name or service not known' which return code is '8'. This error has never happened before even though I was using the library heavily on my testing (if you see the test runner, it uses this library). So first I thought this was because of the re-writing.

So I've checked the code which raises the error message and reached the procedure get-addrinfo. The procedure is just a thin wrapper of getaddrinfo(7) so I've started been doubting. So I've asked my precious body, Google, why this happens, then I've found this Python issue. Sounds pretty much similar. Could it be? So I've wrote the following script to check if this is related to multi threading but my new library.
(import (rnrs) 
        (srfi :18)
        (sagittarius socket))

(define s 
  (thread-start!
   (make-thread
    (lambda ()
      (define sock (make-server-socket "5000"))
      (print 'up)
      (let loop ()
        (let ((cs (socket-accept sock)))
          (socket-send cs (string->utf8 "hello"))
          (loop)))))))

(thread-yield!)
(thread-sleep! 0.1)

(print 'try)

;; uncomment this would make next thread work.
#;(let ((cs (make-client-socket "localhost" "5000")))
  (print (utf8->string (socket-recv cs 255))))

(define c 
  (thread-start!
   (make-thread
    (lambda ()
      (let ((cs (make-client-socket "localhost" "5000")))
        (print (utf8->string (socket-recv cs 255))))))))

(print (thread-join! c))
#|
up
try
Unhandled exception
  Condition components:
  &uncaught-exception
    reason: #<condition
 #<&i/o>
 #<&who get-addrinfo>
 #<&message Name or service not known>
 #<&irritants ((localhost 5000))>
>
    thread: #<thread thread-4 terminated 0x806c8c80>
stack trace:
  [1] thread-join!
  [2] #f
    src: (thread-join! c)
    "test.scm":31
  [3] load
|#
As the comment mentions, if I create a socket on the main thread (or even the thread which creates the server thread?), it works. On POSIX, it says getaddrinfo is thread safe. See, freeaddrinfo, getaddrinfo - get address information. So I think whatever I do on multi threading environment, it should work as I expect. So it's not my library, case closed.

...
......
.........

NO, IT'S NOT!!!

The problem is that Cygwin is one of my most used environment and I can't give up this type of error. However I have no idea how I can resolve this. I've tried to lock before calling getaddrinfo so that the process itself can be atomic. But the result is the same. Now I need to find a workaround for this crappy thing. *sigh*

2015-06-04

スレッドを止める

Schemeでは標準でスレッドに関しての規定はないが、SRFI 18があるので一応ポータブルにマルチスレッドな処理を書ける。サポートしている処理系が少ないのでどこまでポータブルに書けるかというのは多少疑問が残るが・・・

っで、このSRFIはスレッドを止めることを既定していない。理由は知らないけど、いろいろ煩雑になるとかOSレベルのシグナルとかどうするとかじゃないかなぁと思っている。また、一度作成したスレッドの処理を変更することもできない。ちょっと前までこれではスレッドプールを実装できないんじゃないかなぁと思っていたのだが、ちょっとしたアイデアが出てきたのでメモすることにした。(ここまで前置き)

突き詰めてしまうとアクターモデル亜種みたいな感じなんだけど、 とりあえずこんな感じで何とかできる。
(import (scheme base) (scheme write) (srfi 18)
        (util concurrent shared-queue))

(define-record-type thread-container
  (%make-thread-container thread mutex cv stop-request?)
  thread-container?
  (thread thread-container-thread)
  (mutex thread-container-lock)
  (cv thread-container-waiter)
  (stop-request? thread-container-stop-request?
                 thread-container-stop-request-set!))

(define (make-thread-container in out)
  (define tc #f)
  (define (check-stop)
    (when (thread-container-stop-request? tc)
      (mutex-unlock! (thread-container-lock tc)
                     (thread-container-waiter tc))))
  (define (thunk)
    (let loop ()
      (let ((work (shared-queue-get! in)))
        (check-stop)
        (guard (e (else (shared-queue-put! out e) (loop)))
          (shared-queue-put! out (work))
          (loop)))))
  (let ((r (%make-thread-container (thread-start! (make-thread thunk))
                                   (make-mutex)
                                   (make-condition-variable)
                                   #f)))
    (set! tc r)
    r))

(define (stop-thread-container! tc)
  ;; just lock it
  (mutex-lock! (thread-container-lock tc))
  (thread-container-stop-request-set! tc #t))
(define (resume-thread-container! tc)
  (thread-container-stop-request-set! tc #f)
  (condition-variable-broadcast! (thread-container-waiter tc))
  (mutex-unlock! (thread-container-lock tc)))
(util concurrent shared-queue)はSagittariusに入れてあるライブラリでマルチスレッドなキューと思ってもらえばよい。Chibiなら(chibi channel)、GaucheならMT Queueで代用できる。そのうち外だしのライブラリとして公開しようかなぁとも考えているので、R6RSポータブルに書いてあったりもする。
っで、こんな感じで使う。
(define inq (make-shared-queue))
(define outq (make-shared-queue))

(define tc (make-thread-container inq outq))

(display "start") (newline)

(shared-queue-put! inq (lambda () (display "in") (newline) 'work1))
(display (shared-queue-get! outq)) (newline)

(stop-thread-container! tc)

(shared-queue-put! inq (lambda () (display "stopped?") (newline) 'work2))
(display (shared-queue-get! outq 1)) (newline)

(display "ok") (newline)

(resume-thread-container! tc)

(display (shared-queue-get! outq)) (newline)

#|
;; outputs
start
in
work1
#f
ok
stopped?
work2
|#
work2がメインの後に来ているので、スレッドは止まったといえる。まぁ、止まったといっても擬似的にであり、処理の途中で止めることはできないのでその辺は留意する必要がある。

アクターモデルと何が違うの?といわれると多少辛い部分はあるが、擬似的とはいえ処理を止めることができるところだろうか?まぁ、単なる亜種といわれるとそれまでなのだが・・・

このアイデアは(util timer)に入っている(というか、それ書いてて思いついた)。そのうち(util concurrent)にも入れる予定。スレッドを再利用するタイプのExecutorが書けそうな気がしている。