Skip to content

Commit 5982e16

Browse files
authored
Fix: memory leak in transaction handles (#410)
- New handles are no longer allocated when starting new transactions. - Extend transaction testcase
1 parent 4272a26 commit 5982e16

File tree

4 files changed

+98
-19
lines changed

4 files changed

+98
-19
lines changed

connection.go

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ func (conn *Conn) Close() error {
7777

7878
C.OCIHandleFree(unsafe.Pointer(conn.svc), C.OCI_HTYPE_SVCCTX)
7979
C.OCIHandleFree(unsafe.Pointer(conn.errHandle), C.OCI_HTYPE_ERROR)
80+
C.OCIHandleFree(unsafe.Pointer(conn.txHandle), C.OCI_HTYPE_TRANS)
8081
C.OCIHandleFree(unsafe.Pointer(conn.env), C.OCI_HTYPE_ENV)
8182
conn.svc = nil
8283
conn.errHandle = nil
84+
conn.txHandle = nil
8385
conn.env = nil
8486

8587
return err
@@ -157,32 +159,14 @@ func (conn *Conn) BeginTx(ctx context.Context, txOptions driver.TxOptions) (driv
157159
}
158160

159161
if conn.transactionMode != C.OCI_TRANS_READWRITE {
160-
// transaction handle
161-
trans, _, err := conn.ociHandleAlloc(C.OCI_HTYPE_TRANS, 0)
162-
if err != nil {
163-
return nil, fmt.Errorf("allocate transaction handle error: %v", err)
164-
}
165-
166-
// sets the transaction context attribute of the service context
167-
err = conn.ociAttrSet(unsafe.Pointer(conn.svc), C.OCI_HTYPE_SVCCTX, *trans, 0, C.OCI_ATTR_TRANS)
168-
if err != nil {
169-
C.OCIHandleFree(*trans, C.OCI_HTYPE_TRANS)
170-
return nil, err
171-
}
172-
173-
// transaction handle should be freed by something once attached to the service context
174-
// but I cannot find anything in the documentation explicitly calling this out
175-
// going by examples: https://docs.oracle.com/cd/B28359_01/appdev.111/b28395/oci17msc006.htm#i428845
176-
177162
if rv := C.OCITransStart(
178163
conn.svc,
179164
conn.errHandle,
180165
0,
181-
conn.transactionMode, // mode is: C.OCI_TRANS_SERIALIZABLE, C.OCI_TRANS_READWRITE, or C.OCI_TRANS_READONLY
166+
conn.transactionMode|C.OCI_TRANS_NEW, // mode is: C.OCI_TRANS_SERIALIZABLE, C.OCI_TRANS_READWRITE, or C.OCI_TRANS_READONLY
182167
); rv != C.OCI_SUCCESS {
183168
return nil, conn.getError(rv)
184169
}
185-
186170
}
187171

188172
conn.inTransaction = true

globals.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type (
6363
env *C.OCIEnv
6464
errHandle *C.OCIError
6565
usrSession *C.OCISession
66+
txHandle *C.OCITrans
6667
prefetchRows C.ub4
6768
prefetchMemory C.ub4
6869
transactionMode C.ub4

oci8.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ func (drv *DriverStruct) Open(dsnString string) (driver.Conn, error) {
229229
C.OCI_DEFAULT,
230230
)
231231
}
232+
if conn.txHandle != nil {
233+
C.OCIHandleFree(unsafe.Pointer(conn.txHandle), C.OCI_HTYPE_TRANS)
234+
conn.txHandle = nil
235+
}
232236
if conn.usrSession != nil {
233237
C.OCIHandleFree(unsafe.Pointer(conn.usrSession), C.OCI_HTYPE_SESSION)
234238
conn.usrSession = nil
@@ -389,7 +393,19 @@ func (drv *DriverStruct) Open(dsnString string) (driver.Conn, error) {
389393
}
390394
conn.svc = *svcCtxPP
391395
doneLogon = true
396+
}
397+
398+
// Create transaction context.
399+
handle, _, err = conn.ociHandleAlloc(C.OCI_HTYPE_TRANS, 0)
400+
if err != nil {
401+
return nil, fmt.Errorf("allocate transaction handle error: %v", err)
402+
}
403+
conn.txHandle = (*C.OCITrans)(*handle)
392404

405+
// Set transaction context attribute of the service context.
406+
err = conn.ociAttrSet(unsafe.Pointer(conn.svc), C.OCI_HTYPE_SVCCTX, *handle, 0, C.OCI_ATTR_TRANS)
407+
if err != nil {
408+
return nil, fmt.Errorf("service context attribute set error: %v", err)
393409
}
394410

395411
conn.transactionMode = dsn.transactionMode

oci8_sql_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,84 @@ func TestDestructiveTransaction(t *testing.T) {
781781
},
782782
}
783783
testRunQueryResults(t, queryResults)
784+
785+
// Run new transactions after the others finished successfully.
786+
var tx3 *sql.Tx
787+
var tx4 *sql.Tx
788+
789+
tx3, err = TestDB.BeginTx(ctx, nil)
790+
if err != nil {
791+
t.Fatal("begin tx error:", err)
792+
}
793+
794+
tx4, err = TestDB.BeginTx(ctx, nil)
795+
if err != nil {
796+
t.Fatal("begin tx error:", err)
797+
}
798+
799+
result, err = tx4.ExecContext(ctx, "update TRANSACTION_"+TestTimeString+" set B = :1 where A = :2", []interface{}{99, 1}...)
800+
if err != nil {
801+
t.Fatal("exec error:", err)
802+
}
803+
804+
count, err = result.RowsAffected()
805+
if err != nil {
806+
t.Fatal("rows affected error:", err)
807+
}
808+
if count != 1 {
809+
t.Fatalf("rows affected %v not equal to 1", count)
810+
}
811+
812+
result, err = tx3.ExecContext(ctx, "update TRANSACTION_"+TestTimeString+" set B = :1 where A = :2", []interface{}{88, 6}...)
813+
if err != nil {
814+
t.Fatal("exec error:", err)
815+
}
816+
817+
count, err = result.RowsAffected()
818+
if err != nil {
819+
t.Fatal("rows affected error:", err)
820+
}
821+
if count != 1 {
822+
t.Fatalf("rows affected %v not equal to 1", count)
823+
}
824+
825+
queryResults = testQueryResults{
826+
query: "select A, B, C from TRANSACTION_" + TestTimeString + " order by A",
827+
queryResults: []testQueryResult{
828+
{
829+
results: [][]interface{}{
830+
{int64(1), int64(22), int64(3)},
831+
{int64(4), int64(55), int64(6)},
832+
{int64(6), int64(7), int64(8)},
833+
},
834+
},
835+
},
836+
}
837+
testRunQueryResults(t, queryResults)
838+
839+
err = tx3.Commit()
840+
if err != nil {
841+
t.Fatal("commit err:", err)
842+
}
843+
844+
err = tx4.Rollback()
845+
if err != nil {
846+
t.Fatal("commit err:", err)
847+
}
848+
849+
queryResults = testQueryResults{
850+
query: "select A, B, C from TRANSACTION_" + TestTimeString + " order by A",
851+
queryResults: []testQueryResult{
852+
{
853+
results: [][]interface{}{
854+
{int64(1), int64(22), int64(3)},
855+
{int64(4), int64(55), int64(6)},
856+
{int64(6), int64(88), int64(8)},
857+
},
858+
},
859+
},
860+
}
861+
testRunQueryResults(t, queryResults)
784862
}
785863

786864
// TestSelectDualNull checks null from dual

0 commit comments

Comments
 (0)