Skip to content

Commit cda03f8

Browse files
committed
Fix connection memory leak
This is a response to a memory leak that was witnessed in our stress testing. Below is a list of reading that should illuminate this change: http://create.tpsitulsa.com/wiki/V8/Persistent_Handles https://developers.google.com/v8/embed#handles Basically, we were using a persistent handle to hold a reference to ourselves in the Connection class. This prevented the Collection class from being collected properly. The solution is to use a weak persistent handle. The node::ObjectWrap class, which the Connection class inherits from, is a WeakHandle and manages the lifetime properly, calling the destructor (provided it is virtual) of our instance when it is being collected. A destructor was added to the Connection class that closes the ODBC connection if necessary. Since there are now two paths into closing a connection, it is possible for a race condition to arise. A critical section has been put around the code that disconnects from SQL Server to prevent this. For now this critical section code is Windows specific. It will be rewritten as cross platform at a later time. Also, we were potentially leaking a persistent handle to the OdbcConnection class in the OpenOperation class, meaning that the OpenConnection class would never be collected. To fix this, we call the Dispose member function of the persistent handle to notify V8 that we no longer need the pointer. Microsoft Internal Review Id: jaykint-559c37537fb54de28ea1481d9bdf55cb
1 parent afcaf3a commit cda03f8

9 files changed

Lines changed: 153 additions & 25 deletions

‎src/Connection.cpp‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,19 @@ namespace mssql
4444
NODE_SET_PROTOTYPE_METHOD(constructor_template, "beginTransaction", Connection::BeginTransaction);
4545
NODE_SET_PROTOTYPE_METHOD(constructor_template, "commit", Connection::Commit);
4646
NODE_SET_PROTOTYPE_METHOD(constructor_template, "rollback", Connection::Rollback);
47-
NODE_SET_PROTOTYPE_METHOD(constructor_template, "nextResult", Connection::ReadNextResult);
47+
NODE_SET_PROTOTYPE_METHOD(constructor_template, "nextResult", Connection::ReadNextResult);
4848

4949
target->Set(String::NewSymbol("Connection"), constructor_template->GetFunction());
5050

5151
OdbcConnection::InitializeEnvironment();
5252
}
53-
53+
54+
Connection::~Connection( void )
55+
{
56+
// close the connection now since the object is being collected
57+
innerConnection->Collect();
58+
}
59+
5460
Handle<Value> Connection::Close(const Arguments& args)
5561
{
5662
HandleScope scope;
@@ -95,16 +101,18 @@ namespace mssql
95101
return scope.Close<Value>(connection->innerConnection->Rollback( callback ));
96102
}
97103

98-
Handle<Value> Connection::New(const Arguments& args) {
104+
Handle<Value> Connection::New(const Arguments& args)
105+
{
99106
HandleScope scope;
100107

101108
if (!args.IsConstructCall()) {
102109
return Undefined();
103110
}
104111

105-
Connection *s = new Connection();
106-
s->Wrap(args.This());
107-
s->This = Persistent<Object>::New(args.This());
112+
Connection *c = new Connection();
113+
114+
c->Wrap(args.This());
115+
108116
return args.This();
109117
}
110118

‎src/Connection.h‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ namespace mssql
3838
: innerConnection(new OdbcConnectionBridge())
3939
{
4040
}
41-
~Connection()
42-
{
43-
}
41+
42+
virtual ~Connection();
4443

4544
static void Initialize(Handle<Object> target);
4645
static Handle<Value> Close(const Arguments& args);

‎src/CriticalSection.h‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//---------------------------------------------------------------------------------------------------------------------------------
2+
// File: CriticalSection.h
3+
// Contents: Wrapper for a critical section that handles scope
4+
//
5+
// Copyright Microsoft Corporation and contributors
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
//
10+
// You may obtain a copy of the License at:
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//---------------------------------------------------------------------------------------------------------------------------------
19+
20+
namespace mssql {
21+
22+
class CriticalSection {
23+
24+
public:
25+
26+
CriticalSection()
27+
{
28+
InitializeCriticalSection( &handle_ );
29+
}
30+
31+
~CriticalSection()
32+
{
33+
DeleteCriticalSection( &handle_ );
34+
}
35+
36+
void lock( void )
37+
{
38+
EnterCriticalSection( &handle_ );
39+
}
40+
41+
void unlock( void )
42+
{
43+
LeaveCriticalSection( &handle_ );
44+
}
45+
46+
private:
47+
48+
CRITICAL_SECTION handle_;
49+
};
50+
51+
class ScopedCriticalSectionLock {
52+
53+
public:
54+
55+
ScopedCriticalSectionLock( CriticalSection& cs ) :
56+
criticalSection_( cs )
57+
{
58+
criticalSection_.lock();
59+
}
60+
61+
~ScopedCriticalSectionLock( void )
62+
{
63+
criticalSection_.unlock();
64+
}
65+
66+
private:
67+
68+
CriticalSection& criticalSection_;
69+
};
70+
71+
} // mssql

‎src/OdbcConnection.cpp‎

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,26 @@ namespace mssql
4141

4242
bool OdbcConnection::TryClose()
4343
{
44-
if (connectionState != Closed)
44+
if (connectionState != Closed) // fast fail before critical section
4545
{
46-
SQLRETURN ret = SQLDisconnect(connection);
47-
if (ret == SQL_STILL_EXECUTING)
48-
{
49-
return false;
50-
}
51-
if (!SQL_SUCCEEDED(ret))
52-
{
53-
connection.Throw();
54-
}
46+
ScopedCriticalSectionLock critSecLock( closeCriticalSection );
47+
if (connectionState != Closed)
48+
{
49+
SQLRETURN ret = SQLDisconnect(connection);
50+
if (ret == SQL_STILL_EXECUTING)
51+
{
52+
return false;
53+
}
54+
if (!SQL_SUCCEEDED(ret))
55+
{
56+
connection.Throw();
57+
}
5558

56-
statement.Free();
57-
connection.Free();
58-
connectionState = Closed;
59+
resultset.reset();
60+
statement.Free();
61+
connection.Free();
62+
connectionState = Closed;
63+
}
5964
}
6065

6166
return true;

‎src/OdbcConnection.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#pragma once
2121

2222
#include "ResultSet.h"
23+
#include "CriticalSection.h"
2324

2425
namespace mssql
2526
{
@@ -31,6 +32,7 @@ namespace mssql
3132
static OdbcEnvironmentHandle environment;
3233
OdbcConnectionHandle connection;
3334
OdbcStatementHandle statement;
35+
CriticalSection closeCriticalSection;
3436

3537
enum ConnectionStates
3638
{

‎src/OdbcConnectionBridge.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ namespace mssql
4747
return scope.Close(Undefined());
4848
}
4949

50+
void Collect( void )
51+
{
52+
Operation* operation = new CollectOperation(connection);
53+
Operation::Add( operation );
54+
}
55+
5056
Handle<Value> BeginTransaction(Handle<Object> callback )
5157
{
5258
HandleScope scope;

‎src/OdbcOperation.cpp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ namespace mssql
6464

6565
callback->Call(Undefined().As<Object>(), argc, args);
6666
}
67-
68-
callback.Dispose();
6967
}
7068

7169
}

‎src/OdbcOperation.h‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ namespace mssql
4545
{
4646
}
4747

48+
virtual ~OdbcOperation()
49+
{
50+
callback.Dispose();
51+
}
52+
4853
virtual bool TryInvokeOdbc() = 0;
4954
virtual Handle<Value> CreateCompletionArg() = 0;
5055

@@ -58,13 +63,19 @@ namespace mssql
5863
wstring connectionString;
5964
Persistent<Object> backpointer;
6065
public:
61-
OpenOperation(shared_ptr<OdbcConnection> connection, const wstring& connectionString, Handle<Object> callback, Handle<Object> backpointer)
66+
OpenOperation(shared_ptr<OdbcConnection> connection, const wstring& connectionString, Handle<Object> callback,
67+
Handle<Object> backpointer)
6268
: OdbcOperation(connection, callback),
6369
connectionString(connectionString),
6470
backpointer(Persistent<Object>::New(backpointer))
6571
{
6672
}
6773

74+
virtual ~OpenOperation( void )
75+
{
76+
backpointer.Dispose();
77+
}
78+
6879
bool TryInvokeOdbc() override
6980
{
7081
return connection->TryOpen(connectionString);
@@ -195,6 +206,32 @@ namespace mssql
195206

196207
};
197208

209+
class CollectOperation : public OdbcOperation
210+
{
211+
public:
212+
CollectOperation(shared_ptr<OdbcConnection> connection)
213+
: OdbcOperation(connection, Handle<Object>())
214+
{
215+
}
216+
217+
bool TryInvokeOdbc() override
218+
{
219+
return connection->TryClose();
220+
}
221+
222+
Handle<Value> CreateCompletionArg() override
223+
{
224+
assert( false );
225+
HandleScope scope;
226+
return scope.Close( Undefined() );
227+
}
228+
229+
// override to not call a callback
230+
void CompleteForeground() override
231+
{
232+
}
233+
};
234+
198235
class BeginTranOperation : public OdbcOperation
199236
{
200237
public:

‎src/stdafx.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
#include <sqlucode.h>
2828

29+
#include <windows.h> // for critical section until xplatform
30+
2931
#include <vector>
3032
#include <queue>
3133
#include <string>

0 commit comments

Comments
 (0)