Improve error handling; misc

This commit is contained in:
Aditya Manthramurthy 2017-02-03 20:23:10 +05:30
parent aa66ba291e
commit 43bfabd186
5 changed files with 71 additions and 32 deletions

View File

@ -64,6 +64,7 @@ library
, MultiParamTypeClasses , MultiParamTypeClasses
, MultiWayIf , MultiWayIf
, RankNTypes , RankNTypes
, ScopedTypeVariables
, TypeFamilies , TypeFamilies
, TupleSections , TupleSections

View File

@ -11,5 +11,6 @@ module Lib.Prelude
import Protolude as Exports import Protolude as Exports
import Data.Time as Exports (UTCTime) import Data.Time as Exports (UTCTime)
import Data.Maybe as Exports (catMaybes, listToMaybe)
import Control.Monad.Catch as Exports (throwM, MonadThrow, MonadCatch) import Control.Monad.Catch as Exports (throwM, MonadThrow, MonadCatch)

View File

@ -5,7 +5,6 @@ module Network.Minio.PutObject
) where ) where
import qualified Control.Monad.Trans.Resource as R
import qualified Data.Conduit as C import qualified Data.Conduit as C
import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.Binary as CB
import qualified Data.List as List import qualified Data.List as List
@ -44,15 +43,16 @@ data ObjectData m = ODFile FilePath (Maybe Int64) -- ^ Takes filepath and option
-- objects of all sizes, and even if the object size is unknown. -- objects of all sizes, and even if the object size is unknown.
putObject :: Bucket -> Object -> ObjectData Minio -> Minio ETag putObject :: Bucket -> Object -> ObjectData Minio -> Minio ETag
putObject b o (ODFile fp sizeMay) = do putObject b o (ODFile fp sizeMay) = do
isSeekable <- isFileSeekable fp hResE <- withNewHandle fp $ \h -> do
isSeekable <- isHandleSeekable h
handleSizeMay <- getFileSize h
return (isSeekable, handleSizeMay)
-- FIXME: allocateReadFile may return exceptions and shortcircuit (isSeekable, handleSizeMay) <- either (const $ return (False, Nothing)) return
finalSizeMay <- maybe (do (rKey, h) <- allocateReadFile fp hResE
sizeE <- getFileSize h
R.release rKey -- prefer given size to queried size.
return $ hush $ sizeE let finalSizeMay = listToMaybe $ catMaybes [sizeMay, handleSizeMay]
)
(return . Just) sizeMay
case finalSizeMay of case finalSizeMay of
-- unable to get size, so assume non-seekable file and max-object size -- unable to get size, so assume non-seekable file and max-object size
@ -62,11 +62,9 @@ putObject b o (ODFile fp sizeMay) = do
-- got file size, so check for single/multipart upload -- got file size, so check for single/multipart upload
Just size -> Just size ->
if | size <= 64 * oneMiB -> do if | size <= 64 * oneMiB -> do
(rKey, h) <- allocateReadFile fp resE <- withNewHandle fp (\h -> putObjectSingle b o [] h 0 size)
etag <- putObjectSingle b o [] h 0 size either throwM return resE
R.release rKey | size > maxObjectSize -> throwM $ ValidationError $
return etag
| size > maxObjectSize -> R.throwM $ ValidationError $
MErrVPutSizeExceeded size MErrVPutSizeExceeded size
| isSeekable -> parallelMultipartUpload b o fp size | isSeekable -> parallelMultipartUpload b o fp size
| otherwise -> sequentialMultipartUpload b o (Just size) $ | otherwise -> sequentialMultipartUpload b o (Just size) $
@ -93,18 +91,18 @@ parallelMultipartUpload b o filePath size = do
uploadId <- newMultipartUpload b o [] uploadId <- newMultipartUpload b o []
-- perform upload with 10 threads -- perform upload with 10 threads
uploadedParts <- limitedMapConcurrently 10 (uploadPart uploadId) partSizeInfo uploadedPartsE <- limitedMapConcurrently 10 (uploadPart uploadId) partSizeInfo
completeMultipartUpload b o uploadId uploadedParts -- if there were any errors, rethrow exception.
mapM_ throwM $ lefts uploadedPartsE
-- if we get here, all parts were successfully uploaded.
completeMultipartUpload b o uploadId $ rights uploadedPartsE
where where
uploadPart uploadId (partNum, offset, sz) = do uploadPart uploadId (partNum, offset, sz) = withNewHandle filePath $
(rKey, h) <- allocateReadFile filePath \h -> putObjectPart b o uploadId partNum [] $ PayloadH h offset sz
pInfo <- putObjectPart b o uploadId partNum [] $ PayloadH h offset sz
R.release rKey
return pInfo
-- | Upload multipart object from conduit source sequentially without -- | Upload multipart object from conduit source sequentially
-- object size information.
sequentialMultipartUpload :: Bucket -> Object -> Maybe Int64 sequentialMultipartUpload :: Bucket -> Object -> Maybe Int64
-> C.Producer Minio ByteString -> Minio ETag -> C.Producer Minio ByteString -> Minio ETag
sequentialMultipartUpload b o sizeMay src = do sequentialMultipartUpload b o sizeMay src = do
@ -145,3 +143,15 @@ sequentialMultipartUpload b o sizeMay src = do
pInfo <- putObjectPart b o uid partNum [] $ pInfo <- putObjectPart b o uid partNum [] $
PayloadBS $ LB.toStrict buf PayloadBS $ LB.toStrict buf
return $ reverse (pInfo:u) return $ reverse (pInfo:u)
-- | Looks for incomplete uploads for an object. Returns the first one
-- if there are many.
getExistingUpload :: Bucket -> Object
-> Minio (Maybe (UploadId, [ListPartInfo]))
getExistingUpload b o = do
uploadsRes <- listIncompleteUploads' b (Just o) Nothing Nothing Nothing
case uiUploadId <$> listToMaybe (lurUploads uploadsRes) of
Nothing -> return Nothing
Just uid -> do
lpr <- listIncompleteParts' b o uid Nothing Nothing
return $ Just (uid, lprParts lpr)

View File

@ -213,7 +213,7 @@ abortMultipartUpload bucket object uploadId = do
-- | List incomplete multipart uploads. -- | List incomplete multipart uploads.
listIncompleteUploads' :: Bucket -> Maybe Text -> Maybe Text -> Maybe Text listIncompleteUploads' :: Bucket -> Maybe Text -> Maybe Text -> Maybe Text
-> Maybe Text -> Minio ListUploadsResult -> Maybe Text -> Minio ListUploadsResult
listIncompleteUploads' bucket prefix delimiter keyMarker uploadIdMarker = do listIncompleteUploads' bucket prefix delimiter keyMarker uploadIdMarker = do
resp <- executeRequest $ def { riMethod = HT.methodGet resp <- executeRequest $ def { riMethod = HT.methodGet
, riBucket = Just bucket , riBucket = Just bucket

View File

@ -5,6 +5,7 @@ import qualified Control.Concurrent.QSem as Q
import qualified Control.Exception.Lifted as ExL import qualified Control.Exception.Lifted as ExL
import Control.Monad.Trans.Control (liftBaseOp_, StM) import Control.Monad.Trans.Control (liftBaseOp_, StM)
import qualified Control.Monad.Trans.Resource as R import qualified Control.Monad.Trans.Resource as R
import qualified Control.Monad.Catch as MC
import qualified Data.Conduit as C import qualified Data.Conduit as C
import Data.Text.Encoding.Error (lenientDecode) import Data.Text.Encoding.Error (lenientDecode)
import qualified Network.HTTP.Client as NClient import qualified Network.HTTP.Client as NClient
@ -26,17 +27,43 @@ allocateReadFile fp = do
openReadFile f = ExL.try $ IO.openBinaryFile f IO.ReadMode openReadFile f = ExL.try $ IO.openBinaryFile f IO.ReadMode
cleanup = either (const $ return ()) IO.hClose cleanup = either (const $ return ()) IO.hClose
-- | Queries the file size from the handle. Catches any file operation
-- exceptions and returns Nothing instead.
getFileSize :: (R.MonadResourceBase m, R.MonadResource m) getFileSize :: (R.MonadResourceBase m, R.MonadResource m)
=> Handle -> m (Either IOException Int64) => Handle -> m (Maybe Int64)
getFileSize h = ExL.try $ liftIO $ fromIntegral <$> IO.hFileSize h getFileSize h = do
resE <- liftIO $ try $ fromIntegral <$> IO.hFileSize h
case resE of
Left (_ :: IOException) -> return Nothing
Right s -> return $ Just s
isFileSeekable :: (R.MonadResource m, R.MonadResourceBase m) -- | Queries if handle is seekable. Catches any file operation
=> FilePath -> m Bool -- exceptions and return False instead.
isFileSeekable fp = do isHandleSeekable :: (R.MonadResource m, R.MonadResourceBase m)
(rKey, h) <- allocateReadFile fp => Handle -> m Bool
isHandleSeekable h = do
resE <- liftIO $ try $ IO.hIsSeekable h resE <- liftIO $ try $ IO.hIsSeekable h
R.release rKey case resE of
either (throwM . MEFile) return resE Left (_ :: IOException) -> return False
Right v -> return v
-- | Helper function that opens a handle to the filepath and performs
-- the given action on it. Exceptions of type MError are caught and
-- returned - both during file handle allocation and when the action
-- is run.
withNewHandle :: (R.MonadResourceBase m, R.MonadResource m, MonadCatch m)
=> FilePath -> (Handle -> m a) -> m (Either MError a)
withNewHandle fp fileAction = do
-- opening a handle can throw MError exception.
handleE <- MC.try $ allocateReadFile fp
either (return . Left) doAction handleE
where
doAction (rkey, h) = do
-- fileAction may also throw MError exception, so we catch and
-- return it.
resE <- MC.try $ fileAction h
R.release rkey
return resE
lookupHeader :: HT.HeaderName -> [HT.Header] -> Maybe ByteString lookupHeader :: HT.HeaderName -> [HT.Header] -> Maybe ByteString