You are not logged in.
Pages: 1
For months now in live deployment we had reports of 404 errors that we could not reproduce.
I made a small test app, to try reproduce the problem.
It contains a websocket server and client. It has timer with adjustable interval. When timer fires message is sent to server which sends it back to all connected clients.
When client receives the message it adds the latency to memo.
Message contains a timestamp and receiving client calculates difference from timestamp to now, thus latency.
When testing I quickly saw that reducing the interval under 40 ms causes 404 exceptions. This is with single instance - one server and one client in same exe.
Second problem is with several clients, if I run about 4 clients and set interval to 100 ms, after a while again 404 exception happens.
I wonder if you can help me understand what is the problem, as this seems to reproduce the errors we have in live environment.
I include the source for this project:
DFM:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 305
ClientWidth = 393
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object bServer: TSpeedButton
Left = 7
Top = 8
Width = 60
Height = 26
AllowAllUp = True
GroupIndex = 1
Caption = 'Server'
OnClick = Button1Click
end
object bClient: TSpeedButton
Left = 73
Top = 8
Width = 60
Height = 26
AllowAllUp = True
GroupIndex = 2
Caption = 'Client'
OnClick = Button2Click
end
object Memo1: TMemo
Left = 8
Top = 74
Width = 185
Height = 215
ReadOnly = True
TabOrder = 0
end
object eHost: TEdit
Left = 8
Top = 47
Width = 130
Height = 21
TabOrder = 1
Text = 'localhost'
end
object Memo2: TMemo
Left = 199
Top = 74
Width = 185
Height = 215
ReadOnly = True
TabOrder = 2
end
object ePort: TEdit
Left = 144
Top = 47
Width = 38
Height = 21
TabOrder = 3
Text = '9000'
end
object eInterval: TLabeledEdit
Left = 327
Top = 47
Width = 57
Height = 21
EditLabel.Width = 38
EditLabel.Height = 13
EditLabel.Caption = 'Interval'
TabOrder = 4
Text = '100'
end
object Timer1: TTimer
Enabled = False
Interval = 100
OnTimer = Timer1Timer
Left = 264
Top = 40
end
end
PAS
unit frmMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Buttons,
SynCommons,
mORMot,
mORMotHttpClient,
mORMotHttpServer,
SynBidirSock;
type
TOSSServer = class;
TOSSMessage = class;
TOssCallback = class;
IOSSMessage = interface(IInvokable)
['{4F30D230-AA46-4013-A900-33A7B79DD175}']
function GetTimeStamp: TDateTimeMS;
procedure SetTimeStamp(const Value: TDateTimeMS);
property TimeStamp: TDateTimeMS read GetTimeStamp write SetTimeStamp;
end;
TOSSMessage = class(TPersistent)
private
fTimeStamp: TDateTimeMS;
function GetTimeStamp: TDateTimeMS;
procedure SetTimeStamp(const Value: TDateTimeMS);
published
property TimeStamp: TDateTimeMS read GetTimeStamp write SetTimeStamp;
end;
IOSSCallback = interface(IInvokable)
['{C561AB6F-61E0-4009-A1A0-26630A6A6D1F}']
procedure ReceiveOssMessage(const AOSSMessage: TOSSMessage);
end;
IOSSService = interface(IInvokable)
['{DDB2805E-3521-4784-928B-95CE0B9FB4DD}']
procedure SendOssMessage(const AOSSMessage: TOSSMessage);
procedure SubscribeToOss(const ACallback: IOSSCallback);
end;
TForm1 = class(TForm)
Memo1: TMemo;
eHost: TEdit;
Memo2: TMemo;
bServer: TSpeedButton;
bClient: TSpeedButton;
ePort: TEdit;
eInterval: TLabeledEdit;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
fHttpServer: TSQLHttpServer;
fWebSocketServerRest: TWebSocketServerRest;
fServer: TSQLRestServerFullMemory;
fClient: TSQLHttpClientWebsockets;
fService: IOSSService;
fCallback: IOSSCallback;
public
procedure BeforeDestruction; override;
property HttpServer: TSQLHttpServer read fHttpServer;
property WebSocketServerRest: TWebSocketServerRest read fWebSocketServerRest;
property Server: TSQLRestServerFullMemory read fServer;
end;
TOSSServer = class(TInterfacedObject, IOSSService)
private
fClients: TInterfaceList;
protected
procedure SendOssMessage(const AOSSMessage: TOSSMessage);
procedure SubscribeToOss(const ACallback: IOSSCallback);
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
procedure ServiceStart();
published
end;
TOssCallback = class(TInterfacedCallback,IOSSCallback)
private
protected
procedure ReceiveOssMessage(const AOSSMessage: TOSSMessage);
end;
var
Form1: TForm1;
Lock: TRTLCriticalSection;
implementation
{$R *.dfm}
uses
System.DateUtils;
const
TRANSMISSION_KEY = 'OSS_WebSockets';
procedure TForm1.BeforeDestruction;
begin
inherited;
fService := nil;
fCallback := nil;
FreeAndNil(fHttpServer);
FreeAndNil(fServer);
FreeAndNil(fClient);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Port: string;
begin
if not bServer.Down then
begin
FreeAndNil(fHttpServer);
FreeAndNil(fServer);
exit;
end;
fServer := TSQLRestServerFullMemory.CreateWithOwnModel([]);
Server.ServiceDefine(TOSSServer,[IOSSService],sicShared).ByPassAuthentication := true;
Server.CreateMissingTables;
fHttpServer := TSQLHttpServer.Create(ePort.text, [Server], '+', useBidirSocket, 32, secSynShaAes, 'OSS');
fWebSocketServerRest := HttpServer.WebSocketsEnable(Server, TRANSMISSION_KEY, True);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
s: string;
begin
if not bClient.Down then
begin
Timer1.Enabled := false;
fService := nil;
fCallback := nil;
FreeAndNil(fClient);
exit;
end;
fClient := TSQLHttpClientWebsockets.Create(eHost.Text, ePort.Text, TSQLModel.Create([]));
fClient.Model.Owner := fClient;
s := fClient.WebSocketsUpgrade(TRANSMISSION_KEY);
if s <> '' then
raise Exception.Create(s);
if not fClient.ServerTimeStampSynchronize then
raise Exception.CreateFmt(fClient.LastErrorMessage+
'. Error connecting to OSS server at %s:%d', [fClient.HostName, fClient.Port]);
fClient.ServiceDefine([IOSSService],sicShared);
if not fClient.Services.Resolve(IOSSService, fService) then
raise Exception.Create('Service IOSSService unavailable');
fCallback := TOssCallback.Create(fClient, IOssCallback);
fService.SubscribeToOss(fCallback);
Timer1.Enabled := true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
vMessage: TOSSMessage;
begin
vMessage := TOSSMessage.Create;
try
vMessage.TimeStamp := now;
Timer1.Interval := StrToIntDef(eInterval.Text, Timer1.Interval);
try
fService.SendOssMessage(vMessage);
except
on e: exception do
begin
Timer1.Enabled := false;
Form1.Memo2.Lines.Add(e.Message);
raise;
end;
end;
finally
vMessage.Free;
end;
end;
{ TOSSServer }
procedure TOSSServer.AfterConstruction;
begin
inherited;
ServiceStart;
end;
procedure TOSSServer.BeforeDestruction;
begin
inherited;
fClients.Free;
end;
procedure TOSSServer.SendOssMessage(const AOSSMessage: TOSSMessage);
var
i: integer;
begin
for I := 0 to fClients.Count - 1 do
try
(fClients[i] as IOSSCallback).ReceiveOssMessage(AOSSMessage);
except
end;
end;
procedure TOSSServer.ServiceStart;
begin
fClients := TInterfaceList.Create;
end;
procedure TOSSServer.SubscribeToOss(const ACallback: IOSSCallback);
begin
fClients.Add(ACallBack);
end;
{ TOssCallback }
procedure TOssCallback.ReceiveOssMessage(const AOSSMessage: TOSSMessage);
var
LatencyMS: integer;
begin
EnterCriticalSection(Lock);
try
LatencyMS := MilliSecondsBetween(now, AOSSMessage.TimeStamp);
Form1.Memo2.Lines.Add(IntToStr(LatencyMS));
finally
LeaveCriticalSection(Lock);
end;
end;
{ TOSSMessage }
function TOSSMessage.GetTimeStamp: TDateTimeMS;
begin
result := fTimeStamp;
end;
procedure TOSSMessage.SetTimeStamp(const Value: TDateTimeMS);
begin
fTimeStamp := Value;
end;
initialization
TInterfaceFactory.RegisterInterfaces([
TypeInfo(IOSSMessage),
TypeInfo(IOSSService),
TypeInfo(IOSSCallback)]);
InitializeCriticalSection(Lock);
finalization
DeleteCriticalSection(Lock);
end.
It's not string that is the problem, it's the GUID.
It turns out the last signature I posted doesn't work either, but doesn't raise an exception, instead the CallBack param is always nil.
So it's this const AClientID: TClientID that doesn't work
TClientID is a TGUID which is a record:
TGUID = record
D1: Cardinal;
D2: Word;
D3: Word;
D4: array[0..7] of Byte;
end;
From my limited debugging it seems that it fails to read record correctly but this is not detected correctly so it raise exception on following parameter in case of string, or doesn't raise anything at all in case of interface param.
I'm using WebSockets for remote interface calls and think I have found a bug.
So given these types:
TClientID = TGUID;
TSessionID = Int64;
IOSSCallback = invokable interface
I had this method in interface:
function SubscribeToOss(const ACallback: IOSSCallback; const AClientID: TClientID: TSessionId;
Which worked fine, then I extended the params to:
function SubscribeToOss(const ACallback: IOSSCallback; const AClientID: TClientID; const AComputer: string; const AApplicationName: string; const AUserName: string): TSessionId;
And of course recompiled both sides, but Server raises:
IOSSService.SubscribeToOss failed on AComputer:string [missing or invalid value]
Which is not true, the AComputer params is present, but there seems to be a bug in server in param matching.
Then I changed the signature again, just rearranged the order of params into this:
function SubscribeToOss(const AComputer: string; const AApplicationName: string; const AClientID: TClientID; const ACallback: IOSSCallback): TSessionId;
And now it works fine again.
So the problem has something to do with order of params, in particular, having String after Interface or GUID.
I could investigate more, but hope this is enough info for you guys to find the bug.
I'm transferring objects and have found they get destroyed in TServiceMethodExecute.AfterExecute;
I want to keep them alive a bit more and destroy them myself.
Is this possible ?
And 2nd unrelated question:
Can I somehow set TDateTime to be sent with millisecond precision without using SynCommons TDateTimeMS type ?
Regards,
Daniel
After pulling latest I now get:
First chance exception at $00638D4F. Exception class $C0000005 with message 'access violation at 0x00638d4f: read of address 0x00000000'.
SynCommons.bswap64(0)
SynCrypto.TSHA512.Final((...),True)
SynCrypto.THMAC_SHA512.Done((...),False)
SynCrypto.HMAC_SHA512(???,???,???,13,(...))
SynCrypto.HMAC_SHA512(???,???,???)
SynCrypto.PBKDF2_HMAC_SHA512(',4:Òô'#$15'õÃ'#$19'.þ'#$14#2'±]a;)UQik™À'#9#$10#$1E'ÙzûœµOPbñç@)ÚQ%õ\e'#$1D'W'#$10'?›“Ì"''9Õ¦°'#$F'Ü'#$16''')Û¿ƒ‰'#$F'õ'#$9D'E[ÝA)'#$AD'^*·'#1#8'Ú'#4'݉'#$9D'AŒL„7S9Ÿ.P¸8ŒÏ'#4'¬ª.oѺ™i'#$18'SïŽGÉÑ^à•ÃÝ.–µÌ'#$1F'B'#$15,'Developer',16,(...))
SynCrypto.TAESPRNG.Seed
SynCrypto.TAESPRNG.Create(???,1048576,256)
SynCrypto.SetMainAESPRNG
mORMot.TSQLRestServer.Create($2B840B0,False)
mORMot.TSQLRestServerFullMemory.Create(???,False)
mORMot.TSQLRestServer.CreateWithOwnModel(???,False,'root')
32 bit project in XE Berlin.
NOSETTHREADNAME conditional is defined by default in Synopse.inc and apparently no one uses it as it doesn't even compile without it any more.
Thread naming is quite handy feature for debugging and should be fixed and turned on by default, IMHO.
Surely the majority of users have no problem with it.
EDIT: In general it is better to have optional additional features as conditional that turns it *on*, rather that conditional that turns something *off*.
In case of NOSETTHREADNAME for example you have to modify Synopse.inc to comment out NOSETTHREADNAME
If it was simply SETTHREADNAME and it was commented out in Synopse.inc, you could simply define it in project options or another .inc file, without the need to modify Synopse.inc.
As a bonus this is more readable:
{$IFDEF SETTHREADNAME}
than this:
{$IFNDEF NOSETTHREADNAME}
This is not yet merged into master ?
Works now, thanks for the quick fix.
I pulled the latest mormot yesterday, and now I am getting an exception during shutdown.
First chance exception at $77581E0B. Exception class $C0000005 with message 'access violation at 0x77581e0b: write of address 0x00000014'.
SynBidirSock.TWebCrtSocketProcess.SetLastPingTicks(False)
SynBidirSock.TWebCrtSocketProcess.SendFrame((focConnectionClose, [(out of bound) 2,(out of bound) 4,(out of bound) 5,(out of bound) 6], 8235635, ''))
SynBidirSock.TWebSocketProcess.Destroy
SynBidirSock.TWebCrtSocketProcess.Destroy
SynBidirSock.TWebSocketProcessClient.Destroy
System.TObject.Free
SynBidirSock.THttpClientWebSockets.Destroy
System.TObject.Free
mORMotHttpClient.TSQLHttpClientWinSock.InternalClose
mORMot.TSQLRestClientURI.Destroy
Is a bug in mormot, or am I supposed to do something differently ?
Frankly, problems with such basic functionality as disconnect are discouraging.
I introduced Mormot into my company for it's WebSocket implementation but my colleagues now doubt it it was a good choice.
Has anyone tested and confirmed that this works now ?
Both Synopse sockets and websockets seem to implement KeepAlive mechanism.
I'm using WebSockets and I'd like to make use of KeepAlive to detect disconnects.
I tried playing with the available properties but could not get it to do anything.
I assume that once connection is established any subsequent disconnect should be detected after keepAlive period expires.
So, what do I have to do/set for KeepAlive to be in use ?
Excellent, thanks for the help and quick reply.
We have a service application which contains two WebSocket servers.
This morning the service didn't start and the following windows event log was logged:
TSynLog.AutoTable VMT entry already set
So that only gets raised in
class function TSynLog.FamilyCreate: TSynLogFamily;
...
if PPointer(PVMT)^<>nil then
raise ESynException.CreateUTF8('%.AutoTable VMT entry already set',[self]);
We can not reproduce the problem locally and from looking at the code I can't figure out how can it possibly happen, except if both services access TSynLog.Family at the same time.
Do you have any pointers/suggestions as to what could be the problem or how to avoid it ?
We have since added the following as first code in DPR file
begin
TSynLog.Family.Level := [sllException, sllExceptionOS];
TSynLog.Family.AutoFlushTimeOut := 10;
Will that perhaps indirectly fix the problem by ensuring Family is created as first thing the app does ?
Thanks for your time.
I using Mormot in scenario similar to the chat demo 31. Clients send messages and Server should send it to all other clients.
So when server receives a message it iterates a list of connected clients and sends the message to all except client which sent it.
I don't use authentication, and so SessionClosed is not called, is there another way to detect when a client has disconnected ?
Hi,
My use case is chat like - client sending changes to sever which then sends them to all other clients.
I need minimal latency in communication between clients and so I've set LoopDelay of TWebSocketProcessSettings to 0.
Am I correct in assuming LoopDelay = 0 helps ? Is there any downside to doing this ?
Is there anything else that I may do (or not do) to improve latency with WebSockets ?
Thanks
Has anyone done anything more with this ?
Hello,
I'm planning to use Mormot in scenario similar to the chat demo 31. Clients send messages and Server should send it to all other clients.
So basically the only thing I need to do is make sure I don't send the message back to same client that sent it.
I suppose to do this I have to identify sessions, but I don't want full fledged authentication system, I only want each connection to get unique SessionID.
How would I go about doing this ?
Hi,
Is there a way to send attachments ?
Indeed, all gone now, thanks
Yep, that fixed the SynCommons.pas, thanks !
But not the mORMot.pas:
[DCC Warning] mORMot.pas(26912): W1035 Return value of function 'TJSONObjectDecoder.EncodeAsSQLPrepared' might be undefined
Could it be possible to add IFDEF to get rid of the warnings ? Our build system fails on warnings.
[DCC Warning] SynCommons.pas(56189): W1035 Return value of function 'TSynTableFieldProperties.GetValue' might be undefined
[DCC Warning] mORMot.pas(26808): W1035 Return value of function 'TJSONObjectDecoder.EncodeAsSQLPrepared' might be undefined
Pages: 1