I have written a small library for supporting one-shot without using unsfePerformIO...
The library uses SYSV semaphores under linux to make sure the functional argument of
"once" is only ever run once. It uses the ProcessID as the key for the semaphore, so will
even enforce the once-only property accross different Haskell threads. Some semaphore
functions are also exported, allowing other constraints to be used (for example, once
only over multiple processes by using a constant ID rather than the processID.


I have attached the source for the library incase anyone is interested. If people think
it is useful I could put it up on a website (let me know). Also attached is an example,
which can be compiled with:


   ghc -o test NamedSem.hs Test.hs -package posix

   Keean.

{-# OPTIONS -fffi #-}

module NamedSem(once,NamedSem,openNamedSem,closeNamedSem,tasNamedSem) where

import IO
import Foreign.C.Types
import Foreign.Ptr
import Foreign.Storable
import System.Posix.Process

foreign import ccall "wrapper" mkCallback :: IO CInt -> IO (FunPtr (IO CInt))
foreign import ccall unsafe "stdlib.h malloc" mallocSemOp :: CInt -> IO (Ptr SemOp)
foreign import ccall unsafe "stdlib.h free" freeSemOp :: Ptr SemOp -> IO ()
foreign import ccall unsafe "stdlib.h atexit" atexit :: FunPtr (IO CInt) -> IO ()
foreign import ccall unsafe "sys/sem.h semget" semget :: CInt -> CInt -> CInt -> IO CInt
foreign import ccall unsafe "sys/sem.h semop" semop :: CInt -> Ptr SemOp -> CInt -> IO CInt
foreign import ccall unsafe "sys/sem.h semctl" semctl :: CInt -> CInt -> CInt -> IO CInt

data NamedSem = NamedSem CInt deriving Show
data SemOp = SemOp CShort CShort CShort

instance Storable SemOp where
	peek p = do { n <- peekByteOff p 0; o <- peekByteOff p 2; f <- peekByteOff p 4; return (SemOp n o f) }
	poke p (SemOp n o f) = do { pokeByteOff p 0 n; pokeByteOff p 2 o; pokeByteOff p 4 f }
	sizeOf _ = 6
	alignment _ = 2

atExit :: IO CInt -> IO ()
atExit f = do { g <- mkCallback f; atexit g }

openNamedSem :: Int -> IO (Maybe NamedSem)
openNamedSem n = do
	id <- semget (fromIntegral n) 1 0o1660
	if id >= 0
		then do { atExit (semctl id 0 0); return $ Just (NamedSem id) }
		else return $ Nothing

closeNamedSem :: NamedSem -> IO ()
closeNamedSem (NamedSem id) = do
	_ <- semctl id 0 0 -- remove semaphore set
	return ()

copySemOpList :: Ptr SemOp -> [SemOp] -> IO Int
copySemOpList op = foldl
	(\a b -> do { i <- a; pokeElemOff op i b; return (i+1) })
	(return 0)

applySemOpList :: NamedSem -> [SemOp] -> IO Bool
applySemOpList (NamedSem id) ol@(o0:_) = bracket
	(mallocSemOp $ fromIntegral $ sizeOf o0 * length ol)
	freeSemOp $ \op -> do
		i <- copySemOpList op ol
		j <- semop id op (fromIntegral i)
		return (j >= 0)

tasNamedSem :: NamedSem -> IO Bool
tasNamedSem ns = applySemOpList ns [
		SemOp 0 0 0o4000,	-- test for zero
		SemOp 0 1 0			-- add 1 to semaphore
	]

once :: IO () -> IO ()
once f = do
	i <- getProcessID
	s <- openNamedSem (fromIntegral i)
	case s of
		Just n -> do
			b <- tasNamedSem n
			if b then f else return ()
		_ -> error "unable to get semaphore"

{-# OPTIONS -fglasgow-exts #-}

module Main where

import NamedSem

test :: IO ()
test = once $ putStrLn "testing"

main :: IO ()
main = do
	test
	test
	test
	test
	test
	test
_______________________________________________
Haskell-Cafe mailing list
[EMAIL PROTECTED]
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to