2016年7月9日土曜日

filelockをSolarisへ

Solarisにfilelockライブラリを移植する


で、何でこうなったかですが、もともとhaskellのfilelockパッケージがflock関数を内部で利用していたことが事の発端で、Solaris上でbuildするとエラーになる。



...
...
In file included from dist/build/System/FileLock/Internal/Flock_hsc_make.c:1:0:
Flock.hsc: In function 'main':
Flock.hsc:57:16: error: 'LOCK_SH' undeclared (first use in this function)
/usr/gnu/lib/ghc-7.10.3/template-hsc.h:35:10: note: in definition of macro 'hsc_const'
     if ((x) < 0)                                      \
          ^
Flock.hsc:57:16: note: each undeclared identifier is reported only once for each function it appears in
/usr/gnu/lib/ghc-7.10.3/template-hsc.h:35:10: note: in definition of macro 'hsc_const'
     if ((x) < 0)                                      \
          ^
Flock.hsc:58:16: error: 'LOCK_EX' undeclared (first use in this function)
/usr/gnu/lib/ghc-7.10.3/template-hsc.h:35:10: note: in definition of macro 'hsc_const'
     if ((x) < 0)                                      \
          ^
Flock.hsc:61:16: error: 'LOCK_NB' undeclared (first use in this function)
/usr/gnu/lib/ghc-7.10.3/template-hsc.h:35:10: note: in definition of macro 'hsc_const'
     if ((x) < 0)                                      \
          ^
...
...

いつも、忘れたころにやってくる

実際のコードを確認してみる


抜粋しますと
...
flock :: Fd -> Bool -> Bool -> IO Bool
flock (Fd fd) exclusive block = do
  r <- c_flock fd $ modeOp .|. blockOp
  if r == 0
    then return True -- success
    else do
      errno <- getErrno
      case () of
        _ | errno == eWOULDBLOCK
            -> return False -- already taken
          | errno == eINTR
            -> flock (Fd fd) exclusive block
          | otherwise -> throwErrno "flock"
  where
    modeOp = case exclusive of
      False -> #{const LOCK_SH}
      True -> #{const LOCK_EX}
    blockOp = case block of
      True -> 0
      False -> #{const LOCK_NB}
...

動作としては、modeOpで「共有ロック」か「排他ロック」を、blockOpが「LOCK_NB」フラグを追加するかどうかで、「LOCK_NB」が指定されない場合はブロックする可能性があるので「eINTR」に対応していて、その場合は再度ロックが取得できるまで繰り返し、「LOCK_NB」が指定された場合、ロックが取れなかったら素直にIO Falseを返す仕組みになっています。ここを、Solarisで動かしたいって言うことです。

Solarisで動かす


とりあえず全部のコードはgithubにあげて置きますの確認してくださいと言う事で...
https://github.com/calimakvo/filelock.git

flock構造体
Cで定義されているflock構造体をhaskell内で利用するため対応する型をdataで定義する
data CFLock = CFLock {
  l_type   :: !CInt,
  l_whence :: !CInt,
  l_start  :: !COff,
  l_len    :: !COff,
  l_pid    :: !CPid
} deriving(Eq, Ord, Show)

この定義だけでは使えませんので、Storableクラスのインスタンスに登録する、こうすることでghcがCFlock型とflock構造体を相互に変換することが可能になります。
instance Storable CFLock where
  sizeOf x = sizeOf (l_type x)
             + sizeOf (l_whence x)
             + sizeOf (l_start x)
             + sizeOf (l_len x)
             + sizeOf (l_pid x)
  alignment _ = alignment (undefined :: COff)
  peek ptr = CFLock <$> #{peek struct flock, l_type} ptr
                   <*> #{peek struct flock, l_whence} ptr
                   <*> #{peek struct flock, l_start} ptr
                   <*> #{peek struct flock, l_len} ptr
                   <*> #{peek struct flock, l_pid} ptr
  poke ptr (CFLock t w s l p) = do
      #{poke struct flock, l_type} ptr t'
      #{poke struct flock, l_whence} ptr w'
      #{poke struct flock, l_start} ptr s'
      #{poke struct flock, l_len} ptr l'
      #{poke struct flock, l_pid} ptr p'
    where
      t' = fromIntegral t :: CInt
      w' = fromIntegral w :: CInt
      s' = fromIntegral s :: COff
      l' = fromIntegral l :: COff
      p' = fromIntegral p :: CPid

flock関数を置き換える
書き換えた部分だけの抜粋ですが、「EINTR」は「blockOp = True」、「F_SETLKW」で呼んだときに受け取る可能性があるので注意するのと、「exclusive = False」の場合に「F_RDLCK」で呼ばれるので、openFd関数のOpenModeを「WriteRead」にする必要があること(処理を排他するためならちょっと微妙に感じるけど...)。
flock :: Fd -> Bool -> Bool -> IO Bool
flock (Fd fd) exclusive block = do
  flck <- new modeOp
  r <- c_fcntl fd blockOp flck
  if r /= -1
    then return True -- success
    else do
      errno <- getErrno
      case () of
        _ | (errno == Errno #{const EAGAIN} ||
             errno == Errno #{const EACCES})
            -> return False -- already taken
          |  errno == Errno #{const EINTR}
            -> flock (Fd fd) exclusive block
          | otherwise -> throwErrno $ "fcntl(" ++ show(errno) ++ ")"
  where
    modeOp = case exclusive of
      False -> CFLock #{const F_RDLCK} #{const SEEK_SET} 0 0 0
      True -> CFLock #{const F_WRLCK} #{const SEEK_SET} 0 0 0
    blockOp = case block of
      True -> #{const F_SETLKW}
      False -> #{const F_SETLK} -- #{const LOCK_NB}

foreign import ccall "fcntl.h fcntl"
  c_fcntl :: CInt -> CInt -> (Ptr CFLock) -> IO CInt

補足
fcntl関数のプロトタイプ宣言を見ると引数が可変長引数になっています。
int fcntl(int fd, int cmd, ... /* arg */ );

可変長引数をhaskellで表現するのは難しいと思ったので、第3引数がflock構造体を取得する関数に固定化して作ってあります。上手いやり方が見つかりませんでした、方法があったら教えてください。


ついでにhsc拡張子について


このコードはFFI(Foreign Function Interface)といってhaskellのコードから通常のCの関数を呼び出す仕組みになっているため、ちょっと変換が必要。
hsc2hsコマンドを使って普通のhsコードに変換できる、こんな感じ
cuomo@karky7 ~ $ hsc2hs Flock.hsc
ってやるとFlock.hsを作ってくれる、こうすると{#const LOCK_NB}とかが展開されてghciとかでloadできるようになるよ。


ちょっとまて、これがあったじゃないか


いろいろ調べて見たのですが、こういう奴もありました
Prelude> :m +System.Posix.IO
Prelude System.Posix.IO> :m +System.IO
Prelude System.Posix.IO System.IO> :m +System.Posix.Files
Prelude System.Posix.IO System.IO System.Posix.Files>:t setLock
setLock :: System.Posix.Types.Fd -> FileLock -> IO ()
Prelude System.Posix.IO System.IO System.Posix.Files> fd <- openFd "/tmp/lock" ReadWrite (Just stdFileMode) defaultFileFlags
Prelude System.Posix.IO System.IO System.Posix.Files> setLock fd (WriteLock, AbsoluteSeek, 0, 0)
Prelude System.Posix.IO System.IO System.Posix.Files>

こういうのもありです。

ただFFIでやってみたかっただけです、では


2016年7月6日水曜日

もう夏ですよ、年々弱ってます

最近、強風の大室山が綺麗でした


強風で晴天だった大室山を見にいきましたぁ...


 気分良く暑さにもまけ、いつものように飲みましたぁ...


 足元も緑で、虫も一杯です、ムヒバンザァーイ


トドメに、激辛ラーメン10合目でやられました... 屁が痛いです


2016年7月5日火曜日

で、fcntl関数

あの時のflock関数からfcntl関数へ


flock関数が使われているコードをSolarisにそのまま移植する場合は「fcntl関数」を使うようになる。Posix的にはfcntl関数が推奨されているのでこれからファイルロックを書きたいのならfcntl関数で書く方がいいかもね。




fcntl関数


file controlという関数でファイルディスクリプタへ対していろいろな属性や制御を設定できる関数、今回はファイルのロックに関する利用方法だけに絞って確認しています。
詳しい関数の説明はmanページを見てくださいということでサンプルはgithubへ置きましたのでcloneしてmakeしてください。

https://github.com/calimakvo/fcntl.git

cuomo@karky7 ~/fcntl $ tree
.
├── Makefile
├── README.md
├── fcntl_r.c
└── fcntl_rw.c

fcntl関数もアドバイザリロック


これはflock関数と同じです

fcntl関数をファイルロックで使う


fcntl関数とflock関数の大きな違いは、ロック対象のファイルの特定の領域をバイト単位でロックが設定できるところが違います。そのためロック領域を指定するflock構造体が定義されています。

Linuxだとこれ
struct flock
  {
      short int l_type;   /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK.  */
      short int l_whence; /* Where `l_start' is relative to (like `lseek').  */
#ifndef __USE_FILE_OFFSET64
      __off_t l_start;    /* Offset where the lock begins.  */
      __off_t l_len;      /* Size of the locked area; zero means until EOF.  */
#else
      __off64_t l_start;  /* Offset where the lock begins.  */
      __off64_t l_len;    /* Size of the locked area; zero means until EOF.  */
#endif
      __pid_t l_pid;      /* Process holding the lock.  */
  };


Solarisだとこれ
/* regular version, for both small and large file compilation environment */
typedef struct flock {
        short   l_type;
        short   l_whence;
        off_t   l_start;
        off_t   l_len;          /* len == 0 means until end of file */
        int     l_sysid;
        pid_t   l_pid;
        long    l_pad[4];               /* reserve area */
} flock_t;


l_typeメンバにはF_RDLCK(READロック)、F_WRLCK(書込ロック)、F_UNLCK(ロック解除)が指定できます。l_whenceはl_startで指定される位置がファイルのどこなのかを指定するフラグ、SEEK_SET(l_startで指定された位置から)、SEEK_CUR(現在位置)、 SEEK_END(ファイルの終わり)で指定可能です。flock関数のようにファイル全体をロックするなら、l_len = 0でEOFまでロックとして

    ...
    struct flock region;
    region.l_type = F_RDLCK;
    region.l_whence = SEEK_SET;
    region.l_start = 0;   /* ファイル全体をロック */
    region.l_len = 0;     /* l_len = 0でEOFまで指定 */
    ...
    fcntl(lockfd, F_SETLK, &region);
    ...

のような感じになります、これでflock関数と同じになります。

flock関数fcntl関数の挙動の違い


flock関数は「LOCK_NB」フラグを指定しなければロックの取得ができない時点でブロックしましたがfcntl関数は基本的にブロックしない事が特徴です、この時errnoに「EAGEIN」か「EACCES」(処理系に依存)をセットしますのでその場合は再度呼び出してやる必要があります。Posixで移植性を高めるのに両方のエラーを処理できるように書いた方が望ましいそうです。

F_SETLKWを使う


ディフォルトのflock関数と同じような動作にしたい場合は「F_SETLKW」をfcntl関数の呼び出しで指定する。
...
res = fcntl(lockfd, F_SETLKW, &region);
...

「F_SETLKW」フラグはロックが取得できなかった場合、ロックの取得ができるまでブロックするような動きになります、flock関数のディフォルトの動作(「LOCK_NB」を指定しない)と同じになります。

fcntl関数とsignal


ロックの取得ができない場合はfcntl関数呼び出しでブロックするため、「EINTR」(シグナル割込)に備える必要があります。
「fcntl_rw」がブロックする状況を作って別ターミナルから「SIGUSR1」シグナルを送ると「EINTR」で戻ります。
cuomo@karky7 ~ $ kill -USR1 7348

cuomo@karky7 ~/fcntl $ ./fcntl_rw
Get EINTR
Retry lock...
...

ちょっとした注意点


ファイルのオープンの時に、「O_RDONLY」フラグでオープンした際に。fcntl関数へ「F_WRLCK」を指定して呼び出したりすると、「Bad file descriptor」を返してくるのでダメな組み合わせで利用しないようにして下さい。当たり前って言えばそのとおりデスが。

fcntl関数とNFS


勉強不足によって不明、時間がとれたら調べようと思います、すみません。

今後...


やっと「Solaris来たじゃん」って言われそうですが、やっぱりいろんなOSで走るように書くのって難しいのねって感じますね、そもそもfcntl関数の使い方がちょっと違うような気もしますが...でほんとはこの問題の元ネタはhaskellのライブラリがもとでして、そこへつながってしまいます...


汚いコードですみません...


2016年7月3日日曜日

flock関数について調べてみた

いきなりステーキとflock関数


「いきなりステーキ」で、300gのステーキをモリンと食って、ビールをしこたま飲んだ挙句、尿酸値Full Maxな感じで倒れそうですが、flockで目が覚めた最近です。Solarisでゴニョゴニョやっているとちょいちょいflock関数でハマる、Solarisにはflock関数がない。面倒くさいからいつも逃げていたのですが今回はしっかり理解しようと思って、ちょっと調べてみた。だいたい、時間がたつとほぼ忘れますが...



flockはアドバイザリロック


ロックを取得するプロセスやスレッドが協力して動作するロック方式で、強制力のないロック方法です、ルールを守らないプロセスやスレッドが共有リソースへでたらめにアクセスした場合リソースは壊れる可能性があるので気をつけてください

Linuxのflock関数


詳しい関数の説明はmanページを見てくださいということでサンプルはgithubへ置きましたのでcloneしてmakeしてください。

https://github.com/calimakvo/flock.git

cuomo@karky7 ~/flock $ tree 
.
├── Makefile
├── flock.c      「共有ロック(LOCK_SH)」
├── flock_ex.c   「排他ロック(LOCK_EX)」
└── flock_nonblock.c 「ノンブロッキング排他ロック(LOCK_EX|LOCK_NB)」

ロックを確認する


「共有ロック」を使ったロック方式、シグナルをセットした後、flock関数を呼ぶ出すときに「LOCK_SH」を指定して呼び出す。ターミナルから複数起動しても直ぐにロックを取得できる、Read Lockで書き込みする奴が何もいなければ問題ない。
* 1つめ実行
cuomo@karky7 ~/flock $ ./flock
Lock success.
lock fd = 3
main loop..
main loop..
main loop..
..
* 2つめを別ターミナルから実行
cuomo@karky7 ~/flock $ ./flock
Lock success.
lock fd = 3
main loop..
main loop..
main loop..
..

「共有ロック」なので「共有ロック」同士のプロセスなのでロックはすぐとれる

次に2つの「共有ロック」プロセスが動いているところへ「排他ロック」なプロセスを起動してみる
cuomo@karky7 ~/flock $ ./flock_ex
...ロックがとれないのでflock関数でブロックしている

flock_exがflockでブロックしている状態のまま、最初の2つの「共有ロック」なプロセスをCtrl+Cで終了させると
cuomo@karky7 ~/flock $ ./flock_ex
Lock success.
lock fd = 3
main loop..
main loop..
main loop..
...

「排他ロック」のプロセスがロックを取得して実行し出す、その後「共有ロック」なプロセスを複数起動してから...
というような感じで交互に別ターミナルから実行すれば動作が掴めると思います。

LOCK_NBについて


flockで寝てもらいたくない場合に利用するフラグです。flock関数に(LOCK_NB)フラグをor演算子で追加指定して呼び出すと、ロックが取得できなくてもflock関数でブロックせず「EWOULDBLOCK」を返してくる呼び出し方法です。

flock関数とsignal


flock関数が「EINTR」を返すケースっていうのがあります、これはflock関数が呼び出されてブロックしている間にそのプロセスにシグナルが到着した場合、flock関数が中断されエラーで帰ってくる場合があるので、エラーでも「EINTR」の場合はもう一度呼び出してやる必要があります。
flock_exがブロックする状況を作って、別ターミナルからSIGUSR1をflock_exへ送ると、15661はflock_exのPIDです
cuomo@karky7 ~ $ kill -USR1 15661

ロックしているflock_exのターミナルへ「EINTR」を受け取って再度flock関数を呼び出したログが出力されます
cuomo@karky7 ~/flock $ ./flock_ex
get SIGUSR1
Retry get lock. 
..

この時のシグナル動作の注意点としては、通常のsignal関数でSIGUSR1をセットしただけでは「EINTR」で帰って来ないと言うことです(Linuxでの動作なのでBSDあたりだと確認とっていませんが...)、と言うことでPosix的なsigaction関数でシグナルをセットする必要があります。

flock関数とNFS


flock関数はNFS経由で利用した場合はしっかり動かないような処理系があるそうなのでご注意ください、ちなみにSolarisのNFSをLinuxでマウントして同じロック検証(ロックファイルはNFS上に作成)をやって見ましたが問題は起こりませんでした、Linuxのマウント設定とSolarisのNFS設定を確認してみます。

今後...


で「Solaris関係ないじゃん」って言われそうですが、これがfcntl関数へ続く予定です、暫くお待ちください...


汚いコードですみません..