advancedsingleinstance.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. {
  2. This file is part of the Free Component Library (Fcl)
  3. Copyright (c) 2015 by Ondrej Pokorny
  4. Unit implementing Single Instance functionality.
  5. The order of message processing is not deterministic (if there are more
  6. pending messages, the server won't process them in the order they have
  7. been sent to the server.
  8. SendRequest and PostRequest+PeekResponse sequences from 1 client are
  9. blocking and processed in correct order.
  10. See the file COPYING.FPC, included in this distribution,
  11. for details about the copyright.
  12. This program is distributed in the hope that it will be useful,
  13. but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. **********************************************************************}
  16. {$IFNDEF FPC_DOTTEDUNITS}
  17. unit AdvancedSingleInstance;
  18. {$ENDIF FPC_DOTTEDUNITS}
  19. {$mode objfpc}{$H+}
  20. interface
  21. {$IFDEF FPC_DOTTEDUNITS}
  22. uses
  23. System.Classes, System.SysUtils, Fcl.AdvancedIpc, Fcl.SingleInstance;
  24. {$ELSE FPC_DOTTEDUNITS}
  25. uses
  26. Classes, SysUtils, AdvancedIPC, singleinstance;
  27. {$ENDIF FPC_DOTTEDUNITS}
  28. type
  29. TSingleInstanceReceivedCustomMessage = procedure(Sender: TBaseSingleInstance; MsgID: Integer; MsgType: Integer; MsgData: TStream) of object;
  30. TAdvancedSingleInstance = class(TBaseSingleInstance)
  31. private
  32. FGlobal: Boolean;
  33. FID: Ansistring;
  34. FServer: TIPCServer;
  35. FClient: TIPCClient;
  36. FOnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage;
  37. procedure SetGlobal(const aGlobal: Boolean);
  38. procedure SetID(const aID: Ansistring);
  39. protected
  40. procedure DoServerReceivedCustomRequest(const aMsgID: Integer; const aMsgType: Integer; const aStream: TStream);
  41. function GetIsClient: Boolean; override;
  42. function GetIsServer: Boolean; override;
  43. function GetStartResult: TSingleInstanceStart; override;
  44. public
  45. constructor Create(aOwner: TComponent); override;
  46. public
  47. function Start: TSingleInstanceStart; override;
  48. procedure Stop; override;
  49. procedure ServerCheckMessages; override;
  50. procedure ClientPostParams; override;
  51. public
  52. function ClientPostCustomRequest(const aMsgType: Integer; const aStream: TStream): Integer;
  53. function ClientSendCustomRequest(const aMsgType: Integer; const aStream: TStream): Boolean; overload;
  54. function ClientSendCustomRequest(const aMsgType: Integer; const aStream: TStream; out outRequestID: Integer): Boolean; overload;
  55. procedure ServerPostCustomResponse(const aRequestID: Integer; const aMsgType: Integer; const aStream: TStream);
  56. function ClientPeekCustomResponse(const aStream: TStream; out outMsgType: Integer): Boolean;
  57. public
  58. property ID: AnsiString read FID write SetID;
  59. property Global: Boolean read FGlobal write SetGlobal;
  60. property OnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage read FOnServerReceivedCustomRequest write FOnServerReceivedCustomRequest;
  61. end;
  62. implementation
  63. Resourcestring
  64. SErrSetSingleInstanceIDStarted = 'You cannot change the single instance ID when it''s been started.';
  65. SErrSetSingleInstanceGlobalStarted = 'You cannot change the single instance global property when it''s been started.';
  66. SErrStartSingleInstanceStarted = 'You cannot start single instance when it''s been already started.';
  67. SErrSingleInstanceStartResultNotAvailable = 'Single instance hasn''t been started yet.';
  68. SErrSingleInstanceNotClient = 'Current instance is not a client.';
  69. SErrSingleInstanceNotServer = 'Current instance is not a server.';
  70. Const
  71. MSGTYPE_CHECK = -1;
  72. MSGTYPE_CHECKRESPONSE = -2;
  73. MSGTYPE_PARAMS = -3;
  74. MSGTYPE_WAITFORINSTANCES = -4;
  75. { TAdvancedSingleInstance }
  76. constructor TAdvancedSingleInstance.Create(aOwner: TComponent);
  77. var
  78. xID: RawByteString;
  79. I: Integer;
  80. begin
  81. inherited Create(aOwner);
  82. xID := 'SI_'+ExtractFileName(ParamStr(0));
  83. for I := 1 to Length(xID) do
  84. case xID[I] of
  85. 'a'..'z', 'A'..'Z', '0'..'9', '_': begin end;
  86. else
  87. xID[I] := '_';
  88. end;
  89. ID := xID;
  90. end;
  91. function TAdvancedSingleInstance.ClientPeekCustomResponse(
  92. const aStream: TStream; out outMsgType: Integer): Boolean;
  93. begin
  94. if not Assigned(FClient) then
  95. raise ESingleInstance.Create(SErrSingleInstanceNotClient);
  96. Result := FClient.PeekResponse(aStream, outMsgType, TimeOutMessages);
  97. end;
  98. function TAdvancedSingleInstance.ClientPostCustomRequest(
  99. const aMsgType: Integer; const aStream: TStream): Integer;
  100. begin
  101. if not Assigned(FClient) then
  102. raise ESingleInstance.Create(SErrSingleInstanceNotClient);
  103. Result := FClient.PostRequest(aMsgType, aStream);
  104. end;
  105. procedure TAdvancedSingleInstance.ClientPostParams;
  106. var
  107. xSL: TStringList;
  108. xStringStream: TStringStream;
  109. I: Integer;
  110. begin
  111. if not Assigned(FClient) then
  112. raise ESingleInstance.Create(SErrSingleInstanceNotClient);
  113. xSL := TStringList.Create;
  114. try
  115. for I := 0 to ParamCount do
  116. xSL.Add(ParamStr(I));
  117. xStringStream := TStringStream.Create(xSL.DelimitedText);
  118. try
  119. xStringStream.Position := 0;
  120. FClient.PostRequest(MSGTYPE_PARAMS, xStringStream);
  121. finally
  122. xStringStream.Free;
  123. end;
  124. finally
  125. xSL.Free;
  126. end;
  127. end;
  128. function TAdvancedSingleInstance.ClientSendCustomRequest(
  129. const aMsgType: Integer; const aStream: TStream): Boolean;
  130. begin
  131. if not Assigned(FClient) then
  132. raise ESingleInstance.Create(SErrSingleInstanceNotClient);
  133. Result := FClient.SendRequest(aMsgType, aStream, TimeOutMessages);
  134. end;
  135. function TAdvancedSingleInstance.ClientSendCustomRequest(
  136. const aMsgType: Integer; const aStream: TStream; out
  137. outRequestID: Integer): Boolean;
  138. begin
  139. if not Assigned(FClient) then
  140. raise ESingleInstance.Create(SErrSingleInstanceNotClient);
  141. Result := FClient.SendRequest(aMsgType, aStream, TimeOutMessages, outRequestID);
  142. end;
  143. procedure TAdvancedSingleInstance.DoServerReceivedCustomRequest(
  144. const aMsgID: Integer; const aMsgType: Integer; const aStream: TStream);
  145. begin
  146. if Assigned(FOnServerReceivedCustomRequest) then
  147. FOnServerReceivedCustomRequest(Self, aMsgID, aMsgType, aStream);
  148. end;
  149. function TAdvancedSingleInstance.GetIsClient: Boolean;
  150. begin
  151. Result := Assigned(FClient);
  152. end;
  153. function TAdvancedSingleInstance.GetIsServer: Boolean;
  154. begin
  155. Result := Assigned(FServer);
  156. end;
  157. function TAdvancedSingleInstance.GetStartResult: TSingleInstanceStart;
  158. begin
  159. if not(Assigned(FServer) or Assigned(FClient)) then
  160. raise ESingleInstance.Create(SErrSingleInstanceStartResultNotAvailable);
  161. Result := inherited GetStartResult;
  162. end;
  163. procedure TAdvancedSingleInstance.ServerCheckMessages;
  164. var
  165. xMsgID: Integer;
  166. xMsgType: Integer;
  167. xStream: TStream;
  168. xStringStream: TStringStream;
  169. begin
  170. if not Assigned(FServer) then
  171. raise ESingleInstance.Create(SErrSingleInstanceNotServer);
  172. if not FServer.PeekRequest(xMsgID, xMsgType) then
  173. Exit;
  174. case xMsgType of
  175. MSGTYPE_CHECK:
  176. begin
  177. FServer.DeleteRequest(xMsgID);
  178. FServer.PostResponse(xMsgID, MSGTYPE_CHECKRESPONSE, nil);
  179. end;
  180. MSGTYPE_PARAMS:
  181. begin
  182. xStringStream := TStringStream.Create('');
  183. try
  184. FServer.ReadRequest(xMsgID, xStringStream);
  185. DoServerReceivedParams(xStringStream.DataString);
  186. finally
  187. xStringStream.Free;
  188. end;
  189. end;
  190. MSGTYPE_WAITFORINSTANCES:
  191. FServer.DeleteRequest(xMsgID);
  192. else
  193. xStream := TMemoryStream.Create;
  194. try
  195. FServer.ReadRequest(xMsgID, xStream);
  196. DoServerReceivedCustomRequest(xMsgID, xMsgType, xStream);
  197. finally
  198. xStream.Free;
  199. end;
  200. end;
  201. end;
  202. procedure TAdvancedSingleInstance.ServerPostCustomResponse(
  203. const aRequestID: Integer; const aMsgType: Integer;
  204. const aStream: TStream);
  205. begin
  206. if not Assigned(FServer) then
  207. raise ESingleInstance.Create(SErrSingleInstanceNotServer);
  208. FServer.PostResponse(aRequestID, aMsgType, aStream);
  209. end;
  210. procedure TAdvancedSingleInstance.SetGlobal(const aGlobal: Boolean);
  211. begin
  212. if FGlobal = aGlobal then Exit;
  213. if Assigned(FServer) or Assigned(FClient) then
  214. raise ESingleInstance.Create(SErrSetSingleInstanceGlobalStarted);
  215. FGlobal := aGlobal;
  216. end;
  217. procedure TAdvancedSingleInstance.SetID(const aID: Ansistring);
  218. begin
  219. if FID = aID then Exit;
  220. if Assigned(FServer) or Assigned(FClient) then
  221. raise ESingleInstance.Create(SErrSetSingleInstanceIDStarted);
  222. FID := aID;
  223. end;
  224. function TAdvancedSingleInstance.Start: TSingleInstanceStart;
  225. {$IFNDEF MSWINDOWS}
  226. procedure UnixWorkaround(var bServerStarted: Boolean);
  227. var
  228. xWaitRequestID, xLastCount, xNewCount: Integer;
  229. xClient: TIPCClient;
  230. begin
  231. //file locking workaround for UNIX systems -> the server can be started twice if 2 processes are started in parallel
  232. //wait some time to see other clients
  233. FServer.StopServer(False);
  234. xClient := TIPCClient.Create(Self);
  235. try
  236. xClient.ServerID := FID;
  237. xClient.Global := FGlobal;
  238. xWaitRequestID := xClient.PostRequest(MSGTYPE_WAITFORINSTANCES, nil);
  239. xLastCount := -1;
  240. xNewCount := FServer.GetPendingRequestCount;
  241. while xLastCount <> xNewCount do
  242. begin
  243. xLastCount := xNewCount;
  244. Sleep(TimeOutWaitForInstances);
  245. xNewCount := FServer.GetPendingRequestCount;
  246. end;
  247. finally
  248. FreeAndNil(xClient);
  249. end;
  250. //find highest client that will be the server
  251. if FServer.FindHighestPendingRequestId = xWaitRequestID then
  252. begin
  253. bServerStarted := FServer.StartServer(False);
  254. end else
  255. begin
  256. //something went wrong, there are not-deleted waiting requests
  257. //use random sleep as workaround and try to restart the server
  258. Randomize;
  259. Sleep(Random(($3F+PtrInt(GetProcessID)) and $3F));//limit to $3F (63)
  260. bServerStarted := FServer.StartServer(False) and (FServer.GetPendingRequestCount > 0);
  261. end;
  262. end;
  263. {$ENDIF}
  264. var
  265. xStream: TStream;
  266. xMsgType: Integer;
  267. xServerStarted: Boolean;
  268. begin
  269. if Assigned(FServer) or Assigned(FClient) then
  270. raise ESingleInstance.Create(SErrStartSingleInstanceStarted);
  271. FServer := TIPCServer.Create(Self);
  272. FServer.ServerID := FID;
  273. FServer.Global := FGlobal;
  274. xServerStarted := FServer.StartServer(False);
  275. if xServerStarted then
  276. begin//this is single instance -> be server
  277. Result := siServer;
  278. {$IFNDEF MSWINDOWS}
  279. UnixWorkaround(xServerStarted);
  280. {$ENDIF}
  281. end;
  282. if not xServerStarted then
  283. begin//instance found -> be client
  284. FreeAndNil(FServer);
  285. FClient := TIPCClient.Create(Self);
  286. FClient.ServerID := FID;
  287. FClient.Global := FGlobal;
  288. FClient.PostRequest(MSGTYPE_CHECK, nil);
  289. xStream := TMemoryStream.Create;
  290. try
  291. if FClient.PeekResponse(xStream, xMsgType, TimeOutMessages) then
  292. Result := siClient
  293. else
  294. Result := siNotResponding;
  295. finally
  296. xStream.Free;
  297. end;
  298. end;
  299. SetStartResult(Result);
  300. end;
  301. procedure TAdvancedSingleInstance.Stop;
  302. begin
  303. FreeAndNil(FServer);
  304. FreeAndNil(FClient);
  305. end;
  306. initialization
  307. DefaultSingleInstanceClass:=TAdvancedSingleInstance;
  308. end.