advancedsingleinstance.pas 11 KB

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