diff options
author | rowanbeentje <rowan@beent.je> | 2009-02-18 21:47:29 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-02-18 21:47:29 +0000 |
commit | 3ca168863ddbdc3ac3bba0cfd9a64ce262cbfea8 (patch) | |
tree | 19639d2095efd7c3d48ac2dedf893653f8a60c2e | |
parent | 2525366dbfed3aef78beaed89630ea543389cec1 (diff) | |
download | sequelpro-3ca168863ddbdc3ac3bba0cfd9a64ce262cbfea8.tar.gz sequelpro-3ca168863ddbdc3ac3bba0cfd9a64ce262cbfea8.tar.bz2 sequelpro-3ca168863ddbdc3ac3bba0cfd9a64ce262cbfea8.zip |
Sets and enforces a connection timeout, and handles connection timeouts appropriately - offering to retry, reconnect, or disconnect. This fixes Issue #93, Issue #69, and Issue #77.
The gory details:
Previously, MCPKit was correctly running mysql_ping to ensure a connection still existed before running a query, and aborted the query if the connection was no longer active.However the code very rarely checked the response of this, so if a query failed subsequent queries would continue to be run and the program would end up checking non-existent results, throwing Cocoa exceptions and generally breaking. However, mysql_ping would also use the default timeout (30 seconds) for each check - when running the (previous to r333) 14 queries to switch tables, this resulted in a long hang before the program even broke.
To exacerbate the issue, certain situations triggered a bug present in mysql_ping in the old client binaries we're using (http://bugs.mysql.com/bug.php?id=9678), causing mysql_ping to never return despite the presence of a timeout, and so causing an indefinite hang.
This issue has been fixed by:
- Setting a new 10 second connection timeout for both new connections (Issue #69) and for mysql_pings. Once preferences have been redesigned we'll probably make this value editable.
- Enforce the 10 second timeout even if mysql_ping hangs by using interrupts.
- Wrap mysql_ping in a new method to do the above and also catch re-established connections without reporting false failures.
- When a connection has failed, prompt the user to Retry, Reconnect, or Disconnect. Reconnect uses the original details for the old connection to establish a new connection, also attempting to preserve the current encoding.
- Do not return control to the main loop until a connection has been reestablished (or disconnected) - this ensures the program is never in a broken state without having to rewrite all query usage.
Much of the above patches the MCPKit connection methods as necessary.
-rw-r--r-- | Interfaces/English.lproj/ConnectionErrorDialog.xib | 484 | ||||
-rw-r--r-- | Source/CMMCPConnection.h | 31 | ||||
-rw-r--r-- | Source/CMMCPConnection.m | 330 | ||||
-rw-r--r-- | Source/TableDocument.m | 2 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 13 |
5 files changed, 839 insertions, 21 deletions
diff --git a/Interfaces/English.lproj/ConnectionErrorDialog.xib b/Interfaces/English.lproj/ConnectionErrorDialog.xib new file mode 100644 index 00000000..f448beb9 --- /dev/null +++ b/Interfaces/English.lproj/ConnectionErrorDialog.xib @@ -0,0 +1,484 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.03"> + <data> + <int key="IBDocument.SystemTarget">1050</int> + <string key="IBDocument.SystemVersion">9G55</string> + <string key="IBDocument.InterfaceBuilderVersion">677</string> + <string key="IBDocument.AppKitVersion">949.43</string> + <string key="IBDocument.HIToolboxVersion">353.00</string> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="2"/> + </object> + <object class="NSArray" key="IBDocument.PluginDependencies"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + <object class="NSMutableDictionary" key="IBDocument.Metadata"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="1001"> + <string key="NSClassName">NSObject</string> + </object> + <object class="NSCustomObject" id="1003"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="1004"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSWindowTemplate" id="1018659152"> + <int key="NSWindowStyleMask">1</int> + <int key="NSWindowBacking">2</int> + <string key="NSWindowRect">{{196, 355}, {533, 155}}</string> + <int key="NSWTFlags">603979776</int> + <string key="NSWindowTitle">Connection Error</string> + <string key="NSWindowClass">NSWindow</string> + <nil key="NSViewClass"/> + <string key="NSWindowContentMaxSize">{3.40282e+38, 3.40282e+38}</string> + <object class="NSView" key="NSWindowView" id="552671949"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSTextField" id="625523505"> + <reference key="NSNextResponder" ref="552671949"/> + <int key="NSvFlags">274</int> + <string key="NSFrame">{{112, 101}, {404, 34}}</string> + <reference key="NSSuperview" ref="552671949"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="1045633636"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">272629760</int> + <string key="NSContents">Sequel Pro appears to have lost the connection to the server, or the server has stopped responding.</string> + <object class="NSFont" key="NSSupport" id="874654698"> + <string key="NSName">LucidaGrande</string> + <double key="NSSize">1.300000e+01</double> + <int key="NSfFlags">1044</int> + </object> + <reference key="NSControlView" ref="625523505"/> + <object class="NSColor" key="NSBackgroundColor"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MC42NjY2NjY2OQA</bytes> + </object> + </object> + <object class="NSColor" key="NSTextColor"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlTextColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MAA</bytes> + </object> + </object> + </object> + </object> + <object class="NSButton" id="415418827"> + <reference key="NSNextResponder" ref="552671949"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{423, 13}, {96, 32}}</string> + <reference key="NSSuperview" ref="552671949"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="706644831"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Retry</string> + <reference key="NSSupport" ref="874654698"/> + <reference key="NSControlView" ref="415418827"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <string key="NSAlternateContents"/> + <string type="base64-UTF8" key="NSKeyEquivalent">DQ</string> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSButton" id="725257225"> + <reference key="NSNextResponder" ref="552671949"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{318, 12}, {105, 32}}</string> + <reference key="NSSuperview" ref="552671949"/> + <int key="NSTag">1</int> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="884446740"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Reconnect</string> + <reference key="NSSupport" ref="874654698"/> + <reference key="NSControlView" ref="725257225"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSButton" id="946689239"> + <reference key="NSNextResponder" ref="552671949"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{170, 13}, {148, 32}}</string> + <reference key="NSSuperview" ref="552671949"/> + <int key="NSTag">2</int> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="47055839"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Close connection</string> + <reference key="NSSupport" ref="874654698"/> + <reference key="NSControlView" ref="946689239"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSImageView" id="974501507"> + <reference key="NSNextResponder" ref="552671949"/> + <int key="NSvFlags">256</int> + <object class="NSMutableSet" key="NSDragTypes"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="set.sortedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>Apple PDF pasteboard type</string> + <string>Apple PICT pasteboard type</string> + <string>Apple PNG pasteboard type</string> + <string>NSFilenamesPboardType</string> + <string>NeXT Encapsulated PostScript v1.2 pasteboard type</string> + <string>NeXT TIFF v4.0 pasteboard type</string> + </object> + </object> + <string key="NSFrame">{{20, 61}, {75, 74}}</string> + <reference key="NSSuperview" ref="552671949"/> + <bool key="NSEnabled">YES</bool> + <object class="NSImageCell" key="NSCell" id="24661938"> + <int key="NSCellFlags">130560</int> + <int key="NSCellFlags2">33554432</int> + <object class="NSCustomResource" key="NSContents"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">appicon</string> + </object> + <int key="NSAlign">0</int> + <int key="NSScale">0</int> + <int key="NSStyle">0</int> + <bool key="NSAnimates">YES</bool> + </object> + <bool key="NSEditable">YES</bool> + </object> + <object class="NSImageView" id="987221964"> + <reference key="NSNextResponder" ref="552671949"/> + <int key="NSvFlags">256</int> + <object class="NSMutableSet" key="NSDragTypes"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="set.sortedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>Apple PDF pasteboard type</string> + <string>Apple PICT pasteboard type</string> + <string>Apple PNG pasteboard type</string> + <string>NSFilenamesPboardType</string> + <string>NeXT Encapsulated PostScript v1.2 pasteboard type</string> + <string>NeXT TIFF v4.0 pasteboard type</string> + </object> + </object> + <string key="NSFrame">{{70, 57}, {32, 32}}</string> + <reference key="NSSuperview" ref="552671949"/> + <bool key="NSEnabled">YES</bool> + <object class="NSImageCell" key="NSCell" id="572830162"> + <int key="NSCellFlags">130560</int> + <int key="NSCellFlags2">33554432</int> + <object class="NSCustomResource" key="NSContents"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSNetwork</string> + </object> + <int key="NSAlign">0</int> + <int key="NSScale">0</int> + <int key="NSStyle">0</int> + <bool key="NSAnimates">YES</bool> + </object> + <bool key="NSEditable">YES</bool> + </object> + </object> + <string key="NSFrameSize">{533, 155}</string> + <reference key="NSSuperview"/> + </object> + <string key="NSScreenRect">{{0, 0}, {1440, 878}}</string> + <string key="NSMaxSize">{3.40282e+38, 3.40282e+38}</string> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">connectionErrorDialog</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="1018659152"/> + </object> + <int key="connectionID">21</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">closeSheet:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="706644831"/> + </object> + <int key="connectionID">30</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">closeSheet:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="725257225"/> + </object> + <int key="connectionID">31</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">closeSheet:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="946689239"/> + </object> + <int key="connectionID">32</int> + </object> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <object class="NSArray" key="object" id="1002"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="children" ref="1000"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="1001"/> + <reference key="parent" ref="1002"/> + <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="1003"/> + <reference key="parent" ref="1002"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="1004"/> + <reference key="parent" ref="1002"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">1</int> + <reference key="object" ref="1018659152"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="552671949"/> + </object> + <reference key="parent" ref="1002"/> + <string key="objectName">ConnectionErrorDialog</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">2</int> + <reference key="object" ref="552671949"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="974501507"/> + <reference ref="987221964"/> + <reference ref="415418827"/> + <reference ref="625523505"/> + <reference ref="725257225"/> + <reference ref="946689239"/> + </object> + <reference key="parent" ref="1018659152"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">7</int> + <reference key="object" ref="625523505"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1045633636"/> + </object> + <reference key="parent" ref="552671949"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">8</int> + <reference key="object" ref="1045633636"/> + <reference key="parent" ref="625523505"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">9</int> + <reference key="object" ref="415418827"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="706644831"/> + </object> + <reference key="parent" ref="552671949"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">10</int> + <reference key="object" ref="706644831"/> + <reference key="parent" ref="415418827"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">11</int> + <reference key="object" ref="725257225"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="884446740"/> + </object> + <reference key="parent" ref="552671949"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">12</int> + <reference key="object" ref="884446740"/> + <reference key="parent" ref="725257225"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">13</int> + <reference key="object" ref="946689239"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="47055839"/> + </object> + <reference key="parent" ref="552671949"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">14</int> + <reference key="object" ref="47055839"/> + <reference key="parent" ref="946689239"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">26</int> + <reference key="object" ref="974501507"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="24661938"/> + </object> + <reference key="parent" ref="552671949"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">27</int> + <reference key="object" ref="24661938"/> + <reference key="parent" ref="974501507"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">28</int> + <reference key="object" ref="987221964"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="572830162"/> + </object> + <reference key="parent" ref="552671949"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">29</int> + <reference key="object" ref="572830162"/> + <reference key="parent" ref="987221964"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-1.IBPluginDependency</string> + <string>-2.IBPluginDependency</string> + <string>-3.IBPluginDependency</string> + <string>1.IBEditorWindowLastContentRect</string> + <string>1.IBPluginDependency</string> + <string>1.IBWindowTemplateEditedContentRect</string> + <string>1.NSWindowTemplate.visibleAtLaunch</string> + <string>10.IBPluginDependency</string> + <string>11.IBPluginDependency</string> + <string>12.IBPluginDependency</string> + <string>13.IBPluginDependency</string> + <string>14.IBPluginDependency</string> + <string>2.IBPluginDependency</string> + <string>7.IBPluginDependency</string> + <string>8.IBPluginDependency</string> + <string>9.IBPluginDependency</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>{{189, 637}, {533, 155}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>{{189, 637}, {533, 155}}</string> + <boolean value="NO"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">32</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"> + <object class="NSMutableArray" key="referencedPartialClassDescriptions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="NSMutableDictionary" key="actions"> + <string key="NS.key.0">closeSheet:</string> + <string key="NS.object.0">id</string> + </object> + <object class="NSMutableDictionary" key="outlets"> + <string key="NS.key.0">connectionErrorDialog</string> + <string key="NS.object.0">NSWindow</string> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBUserSource</string> + <string key="minorKey"/> + </object> + </object> + </object> + </object> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.LastKnownRelativeProjectPath">../sequel-pro.xcodeproj</string> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + </data> +</archive> diff --git a/Source/CMMCPConnection.h b/Source/CMMCPConnection.h index 7b379d01..3a6b4016 100644 --- a/Source/CMMCPConnection.h +++ b/Source/CMMCPConnection.h @@ -26,6 +26,11 @@ #import <MCPKit_bundled/MCPKit_bundled.h> #import "CMMCPResult.h" +// Set the connection timeout to enforce for all connections - used for the initial connection +// timeout and ping timeouts, but not for long queries/reads/writes. +// Probably worth moving this to a preference at some point. +#define SP_CONNECTION_TIMEOUT 10 + @interface NSObject (CMMCPConnectionDelegate) - (void)willQueryString:(NSString *)query; @@ -34,11 +39,31 @@ @end @interface CMMCPConnection : MCPConnection { + IBOutlet NSWindow *connectionErrorDialog; + NSWindow *parentWindow; id delegate; + + BOOL nibLoaded; + NSString *connectionLogin; + NSString *connectionPassword; + NSString *connectionHost; + int connectionPort; + NSString *connectionSocket; } -- (CMMCPResult *)queryString:(NSString *) query; -- (void)setDelegate:(id)object; -- (NSTimeZone *)timeZone; +- (id) init; +- (id) initToHost:(NSString *) host withLogin:(NSString *) login password:(NSString *) pass usingPort:(int) port; +- (id) initToSocket:(NSString *) socket withLogin:(NSString *) login password:(NSString *) pass; +- (void) initSPExtensions; +- (BOOL) connectWithLogin:(NSString *) login password:(NSString *) pass host:(NSString *) host port:(int) port socket:(NSString *) socket; +- (void) disconnect; +- (BOOL) reconnect; +- (IBAction) closeSheet:(id)sender; +- (void) setParentWindow:(NSWindow *)theWindow; +- (CMMCPResult *) queryString:(NSString *) query; +- (BOOL) checkConnection; +- (void) setDelegate:(id)object; +- (NSTimeZone *) timeZone; +- (BOOL) pingConnection; @end diff --git a/Source/CMMCPConnection.m b/Source/CMMCPConnection.m index 8d4e2673..20f5ba11 100644 --- a/Source/CMMCPConnection.m +++ b/Source/CMMCPConnection.m @@ -23,10 +23,191 @@ // Or mail to <lorenz@textor.ch> #import "CMMCPConnection.h" +#include <unistd.h> +#include <setjmp.h> +static jmp_buf pingTimeoutJumpLocation; +static void forcePingTimeout(int signalNumber); @implementation CMMCPConnection + +/* + * Override the normal init methods, extending them to also init additional details. + */ +- (id) init +{ + [self initSPExtensions]; + self = [super init]; + return self; +} +- (id) initToHost:(NSString *) host withLogin:(NSString *) login password:(NSString *) pass usingPort:(int) port +{ + [self initSPExtensions]; + self = [super initToHost:host withLogin:login password:pass usingPort:port]; + return self; +} +- (id) initToSocket:(NSString *) socket withLogin:(NSString *) login password:(NSString *) pass +{ + [self initSPExtensions]; + self = [super initToSocket:socket withLogin:login password:pass]; + return self; +} + + +/* + * Instantiate extra variables and load the connection error dialog for potential use. + */ +- (void) initSPExtensions +{ + parentWindow = nil; + connectionLogin = nil; + connectionPassword = nil; + connectionHost = nil; + connectionPort = 0; + connectionSocket = nil; + [NSBundle loadNibNamed:@"ConnectionErrorDialog" owner:self]; +} + + +/* + * Override the normal connection method, extending it to also store details of the + * current connection to allow reconnection as necessary. This also sets the connection timeout + * - used for pings, not for long-running commands. + */ +- (BOOL) connectWithLogin:(NSString *) login password:(NSString *) pass host:(NSString *) host port:(int) port socket:(NSString *) socket +{ + if (connectionLogin) [connectionLogin release]; + if (login) connectionLogin = [[NSString alloc] initWithString:login]; + if (connectionPassword) [connectionPassword release]; + if (pass) connectionPassword = [[NSString alloc] initWithString:pass]; + if (connectionHost) [connectionHost release]; + if (host) connectionHost = [[NSString alloc] initWithString:host]; + connectionPort = port; + if (connectionSocket) [connectionSocket release]; + if (socket) connectionSocket = [[NSString alloc] initWithString:socket]; + + if (mConnection != NULL) { + unsigned int connectionTimeout = SP_CONNECTION_TIMEOUT; + mysql_options(mConnection, MYSQL_OPT_CONNECT_TIMEOUT, (const void *)&connectionTimeout); + } + + return [super connectWithLogin:login password:pass host:host port:port socket:socket]; +} + + +/* + * Override the stored disconnection method to ensure that disconnecting clears stored details. + */ +- (void) disconnect +{ + [super disconnect]; + + if (connectionLogin) [connectionLogin release]; + connectionLogin = nil; + if (connectionPassword) [connectionPassword release]; + connectionPassword = nil; + if (connectionHost) [connectionHost release]; + connectionHost = nil; + connectionPort = 0; + if (connectionSocket) [connectionSocket release]; + connectionSocket = nil; +} + + +/* + * Reconnect to the currently "active" - but possibly disconnected - connection, using the + * stored details. + * Error checks extensively - if this method fails, it will ask how to proceed and loop depending + * on the status, not returning control until either a connection has been established or + * the connection and document have been closed. + */ +- (BOOL) reconnect +{ + NSString *currentEncoding = nil; + NSString *currentDatabase = nil; + + // Store the current database and encoding so they can be re-set if reconnection was successful + if (delegate && [delegate valueForKey:@"selectedDatabase"]) { + currentDatabase = [NSString stringWithString:[delegate valueForKey:@"selectedDatabase"]]; + } + if (delegate && [delegate valueForKey:@"_encoding"]) { + currentEncoding = [NSString stringWithString:[delegate valueForKey:@"_encoding"]]; + } + + // Close the connection if it exists. + if (mConnected) { + mysql_close(mConnection); + mConnection = NULL; + } + mConnected = NO; + + // Attempt to reinitialise the connection - if this fails, it will still be set to NULL. + if (mConnection == NULL) { + mConnection = mysql_init(NULL); + } + + if (mConnection != NULL) { + + // Set a connection timeout for the new connection + unsigned int connectionTimeout = SP_CONNECTION_TIMEOUT; + mysql_options(mConnection, MYSQL_OPT_CONNECT_TIMEOUT, (const void *)&connectionTimeout); + + // Attempt to reestablish the connection - using own method so everything gets set up as standard. + // Will store the supplied details again, which isn't a problem. + [self connectWithLogin:connectionLogin password:connectionPassword host:connectionHost port:connectionPort socket:connectionSocket]; + } + + // If the connection was successfully established, reselect the old database and encoding if appropriate. + if (mConnected) { + if (currentDatabase) { + [self selectDB:currentDatabase]; + } + if (currentEncoding) { + [self queryString:[NSString stringWithFormat:@"SET NAMES '%@'", currentEncoding]]; + [self setEncoding:[CMMCPConnection encodingForMySQLEncoding:[currentEncoding UTF8String]]]; + } + } else if (parentWindow) { + + // If the connection was not successfully established, ask how to proceed. + [NSApp beginSheet:connectionErrorDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; + int connectionErrorCode = [NSApp runModalForWindow:connectionErrorDialog]; + [NSApp endSheet:connectionErrorDialog]; + [connectionErrorDialog orderOut:nil]; + + switch (connectionErrorCode) { + + // Should disconnect + case 2: + [parentWindow close]; + return NO; + + // Should retry + default: + return [self reconnect]; + } + } + + return mConnected; +} + + +/* + * Set the parent window of the connection for use with dialogs. + */ +- (void)setParentWindow:(NSWindow *)theWindow { + parentWindow = theWindow; +} + + +/* + * Ends and existing modal session + */ +- (IBAction) closeSheet:(id)sender +{ + [NSApp stopModalWithCode:[sender tag]]; +} + /* Gets a proper NSStringEncoding according to the given MySQL charset. @@ -92,7 +273,7 @@ WARNING : incomplete implementation. Please, send your fixes. if (!strncmp(mysqlEncoding, "sjis", 4)) { return NSShiftJISStringEncoding; } - + // default to iso latin 1, even if it is not exact (throw an exception?) NSLog(@"warning: unknown encoding %s! falling back to latin1.", mysqlEncoding); return NSISOLatin1StringEncoding; @@ -101,7 +282,10 @@ WARNING : incomplete implementation. Please, send your fixes. /* - modified version of queryString to be used in sequel-pro + * Modified version of queryString to be used in Sequel Pro. + * Error checks extensively - if this method fails, it will ask how to proceed and loop depending + * on the status, not returning control until either the query has been executed and the result can + * be returned or the connection and document have been closed. */ - (CMMCPResult *)queryString:(NSString *) query { @@ -109,30 +293,29 @@ WARNING : incomplete implementation. Please, send your fixes. const char *theCQuery = [self cStringFromString:query]; int theQueryCode; - // check connection - if (![self checkConnection]) { - NSLog(@"Connection was gone, but should be reestablished now!"); - } + // If no connection is present, return nil. + if (!mConnected) return nil; + + // Check the connection. This triggers reconnects as necessary, and should only return false if a disconnection + // has been requested - in which case return nil + if (![self checkConnection]) return nil; - // inform the delegate about the query + // Inform the delegate about the query if (delegate && [delegate respondsToSelector:@selector(willQueryString:)]) { [delegate willQueryString:query]; } if (0 == (theQueryCode = mysql_query(mConnection, theCQuery))) { if (mysql_field_count(mConnection) != 0) { - // use CMMCPResult instad of MCPResult + + // Use CMMCPResult instad of MCPResult theResult = [[CMMCPResult alloc] initWithMySQLPtr:mConnection encoding:mEncoding timeZone:mTimeZone]; } else { return nil; } } else { -// NSLog (@"Problem in queryString error code is : %d, query is : %s -in ObjC : %@-\n", theQueryCode, theCQuery, query); -// NSLog(@"Error message is : %@\n", [self getLastErrorMessage]); -// theResult = [theResult init]; // Old version... -// theResult = nil; - - // inform the delegate about errors + + // Inform the delegate about errors if (delegate && [delegate respondsToSelector:@selector(queryGaveError:)]) { [delegate queryGaveError:[self getLastErrorMessage]]; } @@ -142,6 +325,50 @@ WARNING : incomplete implementation. Please, send your fixes. return [theResult autorelease]; } +static void sigalarm(int segnum) { + NSLog(@"BOOOOYAAAAA"); +} +/* + * Checks whether the connection to the server is still active. If not, prompts for what approach to take, + * offering to retry, reconnect or disconnect the connection. + */ +- (BOOL)checkConnection +{ + if (!mConnected) return NO; + + BOOL connectionVerified = FALSE; + + // Check whether the connection is still operational via a wrapped version of MySQL ping. + connectionVerified = [self pingConnection]; + + // If the connection doesn't appear to be responding, show a dialog asking how to proceed + if (!connectionVerified) { + [NSApp beginSheet:connectionErrorDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; + int responseCode = [NSApp runModalForWindow:connectionErrorDialog]; + [NSApp endSheet:connectionErrorDialog]; + [connectionErrorDialog orderOut:nil]; + + switch (responseCode) { + + // "Reconnect" has been selected. Request a reconnect, and retry. + case 1: + [self reconnect]; + return [self checkConnection]; + + // "Disconnect" has been selected. Close the parent window, which will handle disconnections, and return false. + case 2: + [parentWindow close]; + return FALSE; + + // "Retry" has been selected - return a recursive call. + default: + return [self checkConnection]; + } + } + + return connectionVerified; +} + - (void)setDelegate:(id)object { delegate = object; @@ -156,7 +383,7 @@ WARNING : incomplete implementation. Please, send your fixes. NSArray *theRow; id theTZName; NSTimeZone *theTZ; - + [theSessionTZ dataSeek:1ULL]; theRow = [theSessionTZ fetchRowAsArray]; theTZName = [theRow objectAtIndex:1]; @@ -176,7 +403,7 @@ WARNING : incomplete implementation. Please, send your fixes. theTZName = [self stringWithText:theTZName]; } } - + if (theTZName) { // Old versions of the server does not support there own time zone ? theTZ = [NSTimeZone timeZoneWithName:theTZName]; } else { @@ -195,7 +422,7 @@ WARNING : incomplete implementation. Please, send your fixes. NSLog(@"The time zone is not defined on the server, set it to the default one : %@", theTZ); } } - + if (theTZ != mTimeZone) { [mTimeZone release]; mTimeZone = [theTZ retain]; @@ -204,4 +431,71 @@ WARNING : incomplete implementation. Please, send your fixes. return mTimeZone; } -@end + +/* + * The current versions of MCPKit (and up to and including 3.0.1) use MySQL 4.1.12; this has an issue with + * mysql_ping where a connection which is terminated will cause mysql_ping never to respond, even when + * connection timeouts are set. Full details of this issue are available at http://bugs.mysql.com/bug.php?id=9678 ; + * this bug was fixed in 4.1.22 and later versions. + * This issue can be replicated by connecting to a remote host, and then configuring a firewall on that host + * to drop all packets on the connected port - mysql_ping and so Sequel Pro will hang. + * Until the client libraries are updated, this provides a drop-in wrapper for mysql_ping, which calls mysql_ping + * while running a SIGALRM to enforce the specified connection time. This is low-level but effective. + * Unlike mysql_ping, this function returns FALSE on failure and TRUE on success. + */ +- (BOOL) pingConnection +{ + struct sigaction timeoutAction; + NSDate *startDate = [NSDate date]; + BOOL pingSuccess = FALSE; + + // Construct the SIGALRM to fire after the connection timeout if it isn't cleared, calling the forcePingTimeout function. + timeoutAction.sa_handler = forcePingTimeout; + sigemptyset(&timeoutAction.sa_mask); + timeoutAction.sa_flags = 0; + sigaction(SIGALRM, &timeoutAction, NULL); + alarm(SP_CONNECTION_TIMEOUT+1); + + // Set up a "restore point", returning 0; if longjmp is used later with this reference, execution + // jumps back to this point and returns a nonzero value, so this function evaluates to false when initially + // set and true if it's called again. + if (setjmp(pingTimeoutJumpLocation)) { + + // The connection timed out - we want to return false. + pingSuccess = FALSE; + + // On direct execution: + } else { + + // Run mysql_ping, which returns 0 on success, and otherwise an error. + pingSuccess = (BOOL)(! mysql_ping(mConnection)); + + // If the ping failed within a second, try another one; this is because a terminated-but-then + // restored connection is at times restored or functional after a ping, but the ping still returns + // an error. This additional check ensures the returned status is correct with minimal other effect. + if (!pingSuccess && ([[NSDate date] timeIntervalSinceDate:startDate] < 1)) { + pingSuccess = (BOOL)(! mysql_ping(mConnection)); + } + } + + // Reset and clear the SIGALRM used to check connection timeouts. + alarm(0); + timeoutAction.sa_handler = SIG_IGN; + sigemptyset(&timeoutAction.sa_mask); + timeoutAction.sa_flags = 0; + sigaction(SIGALRM, &timeoutAction, NULL); + + + return pingSuccess; +} + +/* + * This function is paired with pingConnection, and provides a method of enforcing the connection + * timeout when mysql_ping does not respect the specified limits. + */ +static void forcePingTimeout(int signalNumber) +{ + longjmp(pingTimeoutJumpLocation, 1); +} + +@end
\ No newline at end of file diff --git a/Source/TableDocument.m b/Source/TableDocument.m index b9cb66a6..10ef7dca 100644 --- a/Source/TableDocument.m +++ b/Source/TableDocument.m @@ -158,6 +158,8 @@ NSString *TableDocumentFavoritesControllerFavoritesDidChange = @"TableDocumentFa password:[passwordField stringValue] usingPort:[portField intValue]]; } + [mySQLConnection setParentWindow:tableWindow]; + if ( ![mySQLConnection isConnected] ) code = 2; if ( !code && ![[databaseField stringValue] isEqualToString:@""] ) { diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 0c4f7839..52a315eb 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ 4DECC48F0EC2B436008D359E /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4DECC3320EC2A170008D359E /* Sparkle.framework */; }; 4DECC4900EC2B436008D359E /* MCPKit_bundled.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4DECC3330EC2A170008D359E /* MCPKit_bundled.framework */; }; 4DECC4910EC2B436008D359E /* Growl.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4DECC3340EC2A170008D359E /* Growl.framework */; }; + 58186D210F4CB38900851FE9 /* ConnectionErrorDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 58186D1F0F4CB38900851FE9 /* ConnectionErrorDialog.xib */; }; 58C56EF50F438E120035701E /* SPDataCellFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C56EF40F438E120035701E /* SPDataCellFormatter.m */; }; 58FEF16D0F23D66600518E8E /* SPSQLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF16C0F23D66600518E8E /* SPSQLParser.m */; }; 58FEF57E0F3B4E9700518E8E /* SPTableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF57D0F3B4E9700518E8E /* SPTableData.m */; }; @@ -248,6 +249,7 @@ 4DECC3320EC2A170008D359E /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Sparkle.framework; sourceTree = "<group>"; }; 4DECC3330EC2A170008D359E /* MCPKit_bundled.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MCPKit_bundled.framework; path = Frameworks/MCPKit_bundled.framework; sourceTree = "<group>"; }; 4DECC3340EC2A170008D359E /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = Frameworks/Growl.framework; sourceTree = "<group>"; }; + 58186D200F4CB38900851FE9 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/ConnectionErrorDialog.xib; sourceTree = "<group>"; }; 58C56EF30F438E120035701E /* SPDataCellFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDataCellFormatter.h; sourceTree = "<group>"; }; 58C56EF40F438E120035701E /* SPDataCellFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDataCellFormatter.m; sourceTree = "<group>"; }; 58FEF16B0F23D66600518E8E /* SPSQLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSQLParser.h; sourceTree = "<group>"; }; @@ -540,6 +542,7 @@ children = ( 17E642060EF020CB001BC333 /* DBView.xib */, 1761FD460EF03A6F00331368 /* MainMenu.xib */, + 58186D1F0F4CB38900851FE9 /* ConnectionErrorDialog.xib */, ); path = Interfaces; sourceTree = "<group>"; @@ -688,6 +691,7 @@ 17E6423E0EF0218B001BC333 /* InfoPlist.strings in Resources */, 1761FD480EF03A6F00331368 /* MainMenu.xib in Resources */, B5E2C5FA0F2353B5007446E0 /* TablePropertyIcon.png in Resources */, + 58186D210F4CB38900851FE9 /* ConnectionErrorDialog.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -777,6 +781,15 @@ name = InfoPlist.strings; sourceTree = "<group>"; }; + 58186D1F0F4CB38900851FE9 /* ConnectionErrorDialog.xib */ = { + isa = PBXVariantGroup; + children = ( + 58186D200F4CB38900851FE9 /* English */, + ); + name = ConnectionErrorDialog.xib; + path = ..; + sourceTree = "<group>"; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ |