2017-11-27 4 views
0

Ich baue eine ODBC-Anwendung in Haskell, mit ausländischen Anrufen. Beim Aufruf von odbc-Funktionen in forkIO- oder forkOS-Threads (dh unbeschränkter oder begrenzter Thread) auf Handles, die demselben Thread zugewiesen sind, gibt die Funktion einen ungültigen Handle-Fehler zurück.unixODBC: ungültiges Handle, wenn Handle in einem Haskell-Thread zugeordnet ist

Der gleiche Anruf funktioniert perfekt, wenn im Hauptthread gemacht.

+0

Du hast DB-Verbindung oder ein DB-Pool nutzen, müssen Sie vielleicht mehr Verbindungshandles, wobei die letzteren Aktien zwischen Threads, wenn Sie eine einzelne Verbindung verwenden Sie müssen einen für jeden Thread erstellen – epsilonhalbe

+0

@epsilonhalbe - ich verwende eine andere Verbindung für jeden Thread; Ich habe versucht, für jeden Thread eine andere Umgebung zu verwenden. Es scheint, dass es ein Fehler in UnixODBC auf 64-Bit-Plattformen ist; Wenn ein Handle im Hauptthread zugewiesen wird, funktioniert es, wenn es auf einem anderen Thread als dem Hauptthread zugewiesen ist, funktionieren sie nicht, und ich denke, das wird nur auf 64-Bit-Plattformen passieren; Ich mache immer noch eine Recherche, bin mir noch nicht sicher –

+0

Sehr seltsam! Wenn Sie sich für den Verbindungspool entscheiden, glaube ich, dass es wirtschaftlicher ist. Haben Sie die db-Einstellungen überprüft, wie viele offene Verbindungen es erlaubt? (Nur um sicher zu sein) – epsilonhalbe

Antwort

1

fand ich, dass das Problem durch einen Fehler in unixODBC verursacht wird, ich es hier dokumentiert: https://sourceforge.net/p/unixodbc/bugs/41/

In wenigen Worten verwendet unixODBC als Zeiger auf Speicher verarbeitet zugeordneten Daten für die Griffe zu halten. Da Handles 32-Bit-Ganzzahlen sind, werden sie bei 64-Bit-Architekturen auf die letzte signifikante Hälfte abgeschnitten (für x86-Prozessoren).

Also, wenn der Zeigerwert kleiner als 2G ist, ist alles in Ordnung, aber wenn es mehr als 2G ist (und nicht 4G aufgrund der Tatsache, dass das Vorzeichen Bit erweitert) dann wird unixODBC nicht in der Lage sein, die Daten zu finden Struktur für das Handle und meldet ungültige Handle.

Wenn SQLAllocHandle von Haskell im Hauptthread aufgerufen wird, hat der zugeordnete Zeiger einen Wert kleiner als 2G, so dass alles funktioniert. Wenn es jedoch von einem anderen Thread (LiftIO oder LiftOS) aufgerufen wird, hat der zugeordnete Zeiger einen Wert größer als 2G, so dass Multithread-ODBC-Anwendungen, die UnixODBC verwenden, in Haskell nicht möglich sind, sofern nicht alle Zuweisungen im Hauptthread vorgenommen werden.

Die Problemumgehung, die ich fand, basiert auf der Tatsache, dass ich im Hauptthread eine Funktion habe, die darauf wartet, dass die Arbeit in den Threads abgeschlossen wird. Ich änderte diese Funktion, um auch auf einen Kanal für Handle-Zuordnungsanforderungen zu warten, und dann das Handle zuzuweisen und die Antwort zurückzugeben.

Dies ist der Beispielcode I für workarround verwendet:

-- All actions are ReaderT monad actions that share a global environment 
-- for threads execution 

-- | wait for worker threads to complete the work 
waitForWorkToEnd :: (MonadIO m) => ReaderT MyEnvironment m() 
waitForWorkToEnd = do 
    threadsCountVar <- asks threads_WorkerThreadsVar 
    allocHandleChan <- asks threads_AllocHandleChan 

    let waitIO = join $ atomically $ orElse (readTVar threadsCountVar >>= check . (<= 0) >> (return $ return())) (allocHandleT allocHandleChan >>= \ io -> return (io >> waitIO)) 
    liftIO $ waitIO 
    liftIO $ log $ fromString $ "all worker threads have finished" 

-- | creates an IO action inside a STM monad to allocate a new handler in the current thread 
allocHandleT :: (MonadIO m, MonadFail m) => TQueue (SQLSMALLINT, SQLINTEGER, TMVar SQLINTEGER) -> STM (m()) 
allocHandleT chan = do 
    (hType, hParent, retVar) <- readTQueue chan 
    return $ allocHandle hType hParent >>= liftIO . atomically . (putTMVar retVar) 

-- | make a handle alloc request to the main thread and wait for result 
allocHandleReq :: (MonadIO m, MonadFail m) => SQLSMALLINT -> SQLINTEGER -> ReaderT MyEnvironment m SQLINTEGER 
allocHandleReq htype hparent = do 
    allocHandleChan <- asks threads_AllocHandleChan 
    resultVar  <- liftIO $ atomically $ newEmptyTMVar 
    liftIO $ atomically $ writeTQueue allocHandleChan (htype, hparent, resultVar) 
    liftIO $ atomically $ takeTMVar resultVar 

-- allocHandle simply calls SQLAllocHandle and takes care of the diagnostics 
-- information; it is part of the sqlcli package you can find it here: 
-- https://hub.darcs.net/mihaigiurgeanu/sqlcli