mausch / FsSql

Functional wrapper around ADO.NET for F#
https://www.openhub.net/p/FsSql
Apache License 2.0
67 stars 15 forks source link

No connection associated with this transaction #23

Closed haf closed 9 years ago

haf commented 9 years ago

Getting a funky exception.

Callee (this crashes, when it gives the SqlConnectionManager as a parameter from the value from the tx workflow builder): screen shot 2015-02-17 at 07 55 56

System.ArgumentNullException: Argument cannot be null.
Parameter name: No connection associated with this transaction
  at Mono.Data.Sqlite.SqliteTransaction.IsValid (Boolean throwError) [0x00011] in /private/tmp/source-mono-mac-3.12.0-branch-32/bockbuild-mono-3.12.0-branch/profiles/mono-mac-xamarin/build-root/mono-3.12.0/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteTransaction.cs:148 
  at Mono.Data.Sqlite.SqliteTransaction.Rollback () [0x00000] in /private/tmp/source-mono-mac-3.12.0-branch-32/bockbuild-mono-3.12.0-branch/profiles/mono-mac-xamarin/build-root/mono-3.12.0/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteTransaction.cs:127 
  at Tx.subscribe@113[Unit,Unit] (Microsoft.FSharp.Core.FSharpFunc`2 f, IDbTransaction tx, Microsoft.FSharp.Core.FSharpFunc`2 onCommit) [0x00000] in <filename unknown>:0 
  at Tx+transactional@125-1[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit].Invoke (IDbConnection conn) [0x00000] in <filename unknown>:0 
  at FsSqlPrelude.withResource[IDbConnection,TxResult`2] (Microsoft.FSharp.Core.FSharpFunc`2 create, Microsoft.FSharp.Core.FSharpFunc`2 dispose, Microsoft.FSharp.Core.FSharpFunc`2 action) [0x00000] in <filename unknown>:0 
  at Tx+Run@132[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit].Invoke (ConnectionManager cmgr) [0x00000] in <filename unknown>:0 
mausch commented 9 years ago

Is that code open source? Could you point me to it?

haf commented 9 years ago

No, sorry, not open source...

I'm trying to reproduce in a unit test, but since most calls in this function go through just fine...

Could you see under what circumstances this would be thrown? It would help me narrow it down.

haf commented 9 years ago

Here's a stack-trace with line numbers:

System.ArgumentNullException: Argument cannot be null.
Parameter name: No connection associated with this transaction
  at Mono.Data.Sqlite.SqliteTransaction.IsValid (Boolean throwError) [0x00011] in /private/tmp/source-mono-mac-3.12.0-branch-32/bockbuild-mono-3.12.0-branch/profiles/mono-mac-xamarin/build-root/mono-3.12.0/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteTransaction.cs:148 
  at Mono.Data.Sqlite.SqliteTransaction.Rollback () [0x00000] in /private/tmp/source-mono-mac-3.12.0-branch-32/bockbuild-mono-3.12.0-branch/profiles/mono-mac-xamarin/build-root/mono-3.12.0/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteTransaction.cs:127 
  at Tx.subscribe@113[Unit,Unit] (Microsoft.FSharp.Core.FSharpFunc`2 f, IDbTransaction tx, Microsoft.FSharp.Core.FSharpFunc`2 onCommit) [0x00066] in c:\prg\FsSql\FsSql\Transactions.fs:121 
  at Tx+transactional@125-1[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit].Invoke (IDbConnection conn) [0x0001c] in c:\prg\FsSql\FsSql\Transactions.fs:130 
  at FsSqlPrelude.withResource[IDbConnection,TxResult`2] (Microsoft.FSharp.Core.FSharpFunc`2 create, Microsoft.FSharp.Core.FSharpFunc`2 dispose, Microsoft.FSharp.Core.FSharpFunc`2 action) [0x0000a] in c:\prg\FsSql\FsSql\prelude.fs:14 
  at Tx+Run@132[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit].Invoke (ConnectionManager cmgr) [0x00026] in c:\prg\FsSql\FsSql\Transactions.fs:134 
haf commented 9 years ago
    member x.Run (f: ConnectionManager -> TxResult<'a,_>) =
        let subscribe (tx: IDbTransaction) (onCommit: IDbTransaction -> 'a -> TxResult<'a,_>) = 
            let r = f (withTransaction tx) // 4, prepares a ConnectionManager that doesn't do anything, invokes f
            // what closes the connection opened in withResource (all of this is in its callback `action`)?
            match r with
            | Commit a -> onCommit tx a
            | Rollback a ->
                tx.Rollback()
                Rollback a
            | Failed e ->
                tx.Rollback() // last FsSql call
                Failed e

        let transactional (conn: IDbConnection) =
            let il = defaultArg isolation IsolationLevel.Unspecified
            let tx = conn.BeginTransaction(il) // 2: this passes
            let onCommit (tran: IDbTransaction) result = 
                tran.Commit()
                Commit result
            subscribe tx onCommit // 3.

        fun cmgr -> 
            match cmgr.tx with
            | None ->
               // 1: inlined to withResource cmgr.create cmgr.dispose transactional
               doWithConnection cmgr transactional
            | Some t -> subscribe t (fun _ -> Commit)
// 5:
 12 /// Creates a <see cref="ConnectionManager"/> with an externally-owned transaction¬
 11 let withTransaction (tx: IDbTransaction): ConnectionManager =¬
 10     let id = Guid.NewGuid().ToString()¬
  9     let create() =·¬
  8         logf "creating connection from const %s" id¬
  7         tx.Connection¬
  6     let dispose c = logf "disposing connection (but not really) %s" id¬
  5     ConnectionManager.make(create, dispose, tx)¬

withResource

  8 let withResource create dispose action =^M¬
  9     let x = create()^M¬
 10     try^M¬
 11         action x^M¬
 12     finally^M¬
 13         dispose x^M¬
haf commented 9 years ago

How about a bit more logging infrastructure? I'd be interested in the trace that's currently hard-coded to ()?

haf commented 9 years ago

It might have something to do with multiple Tx.execNonQueryi calls in a tx {}.

haf commented 9 years ago

I believe this issue has earned the official Yak-shaving reward!

dsc_0244

haf commented 9 years ago

It also seems that the Tx functionality makes it so that exceptions aren't always raised properly.

haf commented 9 years ago

Repro, finally:

module logibit.Sqlite.Tests.ExternalBugs

open Fuchu
open NodaTime
open System
open logibit.Sqlite

let prepare () =
  let mgr = Sql.withNewConnection (fun () -> Conn.sqlite_open' InMemory)
  Sql.execNonQuery mgr "
CREATE TABLE some_table
  ( id TEXT CONSTRAINT pk_id PRIMARY KEY,
    required_field TEXT NOT NULL )"
    []
  |> ignore
  mgr, Sql.Parameter.make, Tx.TransactionBuilder()

[<Tests>]
let tests =
  testList "https://github.com/mausch/FsSql/issues/23" [
    testCase "'No connection associated with' ... nested with outer error" <| fun _ ->
      let mgr, P, tx = prepare ()

      let nested (f_cont : Sql.ConnectionManager -> _) =
        tx {
          let! ret = f_cont
          return ret
        }
      let inner =
        tx {
          do! Tx.execNonQueryi "insert into some_table(id) values(@id)"
                               [ P("@id", "290345632deadbeef") ]
        }

      match nested inner mgr with
      | Tx.Commit _ -> ()
      | other -> Tests.failtestf "got %A" other
  ]

Output:

https://github.com/mausch/FsSql/issues/23/'No connection associated with' ... nested with outer error: Exception: System.ArgumentNullException: Argument cannot be null.
Parameter name: No connection associated with this transaction
  at Mono.Data.Sqlite.SqliteTransaction.IsValid (Boolean throwError) [0x00011] in /private/tmp/source-mono-mac-3.12.0-branch-32/bockbuild-mono-3.12.0-branch/profiles/mono-mac-xamarin/build-root/mono-3.12.0/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteTransaction.cs:148 
  at Mono.Data.Sqlite.SqliteTransaction.Rollback () [0x00000] in /private/tmp/source-mono-mac-3.12.0-branch-32/bockbuild-mono-3.12.0-branch/profiles/mono-mac-xamarin/build-root/mono-3.12.0/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteTransaction.cs:127 
  at Tx.subscribe@113[Unit,Object] (Microsoft.FSharp.Core.FSharpFunc`2 f, IDbTransaction tx, Microsoft.FSharp.Core.FSharpFunc`2 onCommit) [0x00000] in <filename unknown>:0 
  at Tx+transactional@125-1[Microsoft.FSharp.Core.Unit,System.Object].Invoke (IDbConnection conn) [0x00000] in <filename unknown>:0 
  at FsSqlPrelude.withResource[IDbConnection,TxResult`2] (Microsoft.FSharp.Core.FSharpFunc`2 create, Microsoft.FSharp.Core.FSharpFunc`2 dispose, Microsoft.FSharp.Core.FSharpFunc`2 action) [0x00000] in <filename unknown>:0 
  at Tx+Run@132[Microsoft.FSharp.Core.Unit,System.Object].Invoke (ConnectionManager cmgr) [0x00000] in <filename unknown>:0 
  at Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.FSharpFunc`2[SqlModule+ConnectionManager,Tx+TxResult`2[Microsoft.FSharp.Core.Unit,System.Object]],SqlModule+ConnectionManager].InvokeFast[TxResult`2] (Microsoft.FSharp.Core.FSharpFunc`2 func, Microsoft.FSharp.Core.FSharpFunc`2 arg1, ConnectionManager arg2) [0x00000] in <filename unknown>:0 
  at logibit.Sqlite.Tests.ExternalBugs+tests@21.Invoke (Microsoft.FSharp.Core.Unit _arg1) [0x00041] in /src/logibit.sqlite.tests/ExternalBugs.fs:35 
  at Fuchu.Impl+evalTestList@258-1.Invoke (System.Tuple`2 tupledArg) [0x00000] in <filename unknown>:0  (00:00:00.1288380)
haf commented 9 years ago

Did you manage to repro from this?

haf commented 9 years ago

Ping

mausch commented 9 years ago

I just took a look and the problem here is definitely with the TransactionBuilder.Run method. This is the one that does the actual Rollback/Commit on the real IDbTransaction object. The problem is that this particular arrangement in the test calls Run twice. As the first call to Run closes the transaction (either by calling Commit or Rollback on IDbTransaction), the second call tries to use the same transaction but fails because it has been already closed.

mausch commented 9 years ago

Fixed in aca1d81fa0786f3898db44e550c123af546206f8

haf commented 9 years ago

:ice_cream: