-- Copyright (c) 2013, GREE, Inc. All rights reserved. -- authors: Kiyoshi Ikehara {-# LANGUAGE OverloadedStrings, BangPatterns #-} module Main where import Prelude hiding (lookup) import Control.Monad.Trans import qualified Data.ByteString.Char8 as BS import Data.Conduit import Data.Conduit.Network import Data.Conduit.Memcache import qualified Data.HashTable.IO as H import Control.Monad import Control.Concurrent hiding (yield) import Data.Word import Control.Monad.Trans.Resource import Network.Memcache.Op import Network.Memcache.Response type Key = BS.ByteString type Version = Word64 type Value = (Version, BS.ByteString) type HashTable k v = H.BasicHashTable k v main :: IO () main = do ht <- H.new :: IO (HashTable Key Value) htVar <- newMVar ht runTCPServer (serverSettings 11211 "*") $ \appData -> do runResourceT $ do (appSource appData) $$ getOpText =$ process htVar =$ putResponseText =$ (appSink appData) process :: MVar (HashTable Key Value) -> ConduitM (Either BS.ByteString Op) Response (ResourceT IO) () process htVar = loop where loop :: ConduitM (Either t Op) Response (ResourceT IO) () loop = do meOp <- await case meOp of Nothing -> return () Just (Left _msg) -> yield Error >> loop Just (Right op) -> case op of SetOp key _flags _exptime _bytes value options -> do with $ \ht -> do mValue <- lookup ht key case mValue of Just (version', _) -> insert ht key (version' + 1, value) Nothing -> insert ht key (0, value) yield' options Stored loop CasOp key _flags _exptime _bytes version value options -> do with $ \ht -> do mValue <- lookup ht key case mValue of Nothing -> yield' options NotFound Just (version', _) -> case version == version' of True -> do insert ht key (version' + 1, value) yield' options Stored False -> yield' options Exists loop AddOp key _flags _exptime _bytes value options -> do with $ \ht -> do mValue <- lookup ht key case mValue of Nothing -> do insert ht key (1, value) yield' options Stored Just _ -> yield' options NotStored loop ReplaceOp key _flags _exptime _bytes value options -> do with $ \ht -> do mValue <- lookup ht key case mValue of Nothing -> yield' options NotStored Just (version', _value') -> do insert ht key (version' + 1, value) yield' options Stored loop AppendOp key flags exptime bytes value options -> append' True key flags exptime bytes value options >> loop PrependOp key flags exptime bytes value options -> append' False key flags exptime bytes value options >> loop GetOp keys -> processGet False keys >> loop GetsOp keys -> processGet True keys >> loop DeleteOp key options -> do with $ \ht -> do delete ht key yield' options Deleted loop IncrOp key value options -> incr' True key value options >> loop DecrOp key value options -> incr' False key value options >> loop TouchOp key _exptime options -> do with $ \ht -> do mValue <- lookup ht key case mValue of Nothing -> yield' options NotFound Just (_, _value) -> yield' options Touched -- XXX loop PingOp -> yield Ok >> loop FlushAllOp -> do liftIO $ takeMVar htVar >> H.new >>= putMVar htVar yield Ok loop VersionOp -> yield (Version "hemcached-0.0.1") >> loop QuitOp -> return () StatsOp _args -> yield End >> loop incr' isIncr key value options = do with $ \ht -> do mValue <- lookup ht key case mValue of Nothing -> yield' options NotFound Just (version', value') -> do let r = if isIncr then read (BS.unpack value') + value else read (BS.unpack value') - value insert ht key (version' + 1, BS.pack $ show r) yield' options $ Code r append' isAppend key _flags _exptime _bytes value options = do with $ \ht -> do mValue <- lookup ht key case mValue of Nothing -> yield' options NotStored Just (version', value') -> do insert ht key (version' + 1, BS.concat $ if isAppend then [value', value] else [value, value']) yield' options Stored processGet _ [] = yield End processGet withVersion (key:rest) = do with $ \ht -> do mValue <- lookup ht key case mValue of Just (version, value) -> do yield (Value key 0 (fromIntegral $ BS.length value) value (if withVersion then Just version else Nothing)) Nothing -> return () processGet withVersion rest yield' options resp = when (Noreply `notElem` options) $ yield resp delete ht key = liftIO $ H.delete ht key lookup ht key = liftIO $ H.lookup ht key insert ht key value = liftIO $ H.insert ht key value with act = bracketP lock unlock act lock = liftIO $ takeMVar htVar unlock ht = liftIO $ putMVar htVar ht