4

This is my first post here - so be gentle :-)

I want to build a client/server application that uses datasnap for data transport. This is a fairly simple task - and there are lots of examples to learn from. BUT - Having a Datasnap server (build from Delphi XE wizard) I find myself running into a problem, and I hope someone can guide me into the right direction.

Server and Client run on same PC (that is the design for now). Server is running Session lifecycle. Server and Client shares a class (posted below)..

The Server provides a simple method - GetServerObject which uses the GetNewObject method. The Server itself is a VCL application - main form is fmServer. OnCreate instatiates the Servers FormObject property (FormObject := TMyDataObject.Create);

function TServerMethods2.GetNewObject: TMyDataObject;
begin
  Result := TMyDataObject.Create;
end;

function TServerMethods2.GetServerObject: TMyDataObject;
begin
  Result := GetNewObject;
  if not Result.Assign(fmServer.FormObject) then
    raise Exception.Create('Server error : Assign failed!');
end;

All this is pretty trivial - and my problem only appears if I twist my Client application into a multithreaded monster :-) (read - more than 1 thread).

So here is the Thread code for the client.

  TDataThread = class(TThread)
  private
    DSConn: TSQLConnection;
  protected
    procedure Execute; override;
  public
    constructor Create(aConn: TSQLConnection); overload;
  end;

constructor TDataThread.Create(aConn: TSQLConnection);
begin
  inherited Create(False);
  DSConn := aConn.CloneConnection;
  FreeOnTerminate := true;
end;

procedure TDataThread.Execute;
var
  DSMethod: TServerMethods2Client;
  aDataObject : TMyDataObject;
begin
  NameThreadForDebugging('Data');
  { Place thread code here }
  DSMethod := nil;
  try
    while not terminated do
    begin
      sleep(10);
      if DSConn.Connected then
      begin
        try
          if DSMethod = nil then
            DSMethod := TServerMethods2Client.Create(DSConn.DBXConnection,false);
          if DSMethod <> nil then
            try
              aDataObject := DSMethod.GetserverObject;
            finally
              freeandnil(aDataObject);
            end;
        except
          freeandnil(DSMethod);
          DSConn.Connected := False;
        end
      end
      else
      begin
        // connect
        try
          sleep(100);
          DSConn.Open;
        except
          ;
        end;
      end;
    end;
  finally
    freeandnil(DSMethod);
    DSConn.Close;
    freeandnil(DSConn);
  end;

When I create more than 1 of these threads - eventually I will get an error (being "cannot instatiate ... " or some "remote dbx error ..." .. and so on.

I simply cannot get this to work - so that I can spawn hundreds of threads/connections to a datasnap server.

I know this question is tricky - but my hope is that someone is smarter than me :-)

If I try the same client thread code - but accessing a more simple server method (lets say echostring from sample) then I can run it with hundreds of threads. Perhaps Im answering myself here - but Im too blind to realize it :-)

unit uDataObject;

interface

uses
  SysUtils;

Type
  TMyDataObject = class(TObject)
  private
    fString: String;
    fInteger: Integer;
  public
    constructor Create; virtual;
    destructor Destroy; override;
    function Assign(aSource: TMyDataObject): boolean;
    property aString: String read fString write fString;
    property aInteger: Integer read fInteger write fInteger;
  end;

implementation

{ TMyDataObject }

function TMyDataObject.Assign(aSource: TMyDataObject): boolean;
begin
  if aSource <> nil then
  begin
    try
      fString := aSource.aString;
      fInteger := aSource.aInteger;
      Result := True;
    except
      Result := false;
    end;
  end
  else
    Result := false;
end;

constructor TMyDataObject.Create;
begin
  inherited;
  Randomize;
  fString := 'The time of creation is : ' + FormatDateTime('ddmmyyyy hh:nn:ss:zzz', Now);
  fInteger := Random(100);
end;

destructor TMyDataObject.Destroy;
begin
  inherited;
end;
end.

All help is appreciated

Bimmer_R
  • 588
  • 9
  • 18
  • 1
    Probably unrelated but I noticed your `TDataThread` constructor calls `inherited Create(False)` which will immediately run the `Execute` method in the context of a new thread before you clone the connection. – Ondrej Kelle Apr 07 '11 at 11:32
  • @TOndrej: unrelated or not, it is a problem. We have made it a policy to create all threads suspended. In fact, our TThread descendents have constructors that do not allow for the "CreateSuspended" parameter and do not even allow calling the normal constructor (raises an exception)... – Marjan Venema Apr 07 '11 at 11:47
  • 1
    You shouldn't call Randomize in the constructor. Only one call to Randomize during the startup of your application will do. – Uwe Raabe Apr 07 '11 at 11:49
  • @Marjan Venema Interesting, I usually just initialize the thread instance data **before** calling inherited constructor, not suspended. – Ondrej Kelle Apr 07 '11 at 11:49
  • Hi. Im now calling randomize in onformcreate event. And creating the thread suspended. I just want the thread to run as soon as possible - so I call resume as last statement in constructor - this gives me a warning about deprecated. But deprecated is not the same as "not working" :-) But it still throws "invalid pointer operation ..." ... and breaks in client at the GetServerObject call. – Bimmer_R Apr 07 '11 at 12:11
  • @Ondrej: We just ran into trouble with that once too often. Somebody would forget to do things in the correct order. Possibly because normally you would call inherited and then do your own stuff. Also, while it does require the class that instantiates the thread to call Start (previously Resume), it does give it a chance to set other stuff before giving the thread a kick. Which is useful when you have a class hierarchy of descendants and descendants need more than the ancestor constructor provides and you cannot or don't want to use a specific constructor because of factories/frameworks etc. – Marjan Venema Apr 07 '11 at 12:16
  • The weird thing is that it runs rock solid if I access a server method that returns a simple datastructure - a String fx. So Im wondering if there is some limitations in the whole marshall/unmarshall thingy that happens when using a more complex return value? Im not sure - but I think that the datasnap server should be threadsafe .. or am I barking up the wrong tree here? – Bimmer_R Apr 07 '11 at 14:17
  • Some indication could be that it runs smooth as long as I only use one thread. As soon as I spawn a second ... it will fail - not imidiately, but eventually. – Bimmer_R Apr 07 '11 at 14:19
  • More info : Using TCPView I can see that running with 1 thread (1 connection) its stable ... adding the second thread will eventually close/break a connection or something. It re establishes itself - but that I think is where my problem lies. But why does the connection get torn down? Again - running 1 thread keeps running on the same port ... ?? Still puzzled. – Bimmer_R Apr 07 '11 at 14:38
  • I have now committed it to QualityCentral - http://qc.embarcadero.com/wc/qcmain.aspx?d=92921 – Bimmer_R Apr 07 '11 at 19:18
  • @Marjan and TOndrej: inherited Create(False) does indeed call the inherited thread constructor. However, the thread will not start executing until the entire class constructor has completed. This means the code in TDataThread.Create will always completely execute before the thread starts, regardless of the value of the CreateSuspended flag passed to the inherited constructor. – Jon Robertson Sep 10 '11 at 13:07
  • @Jon: that was fixed Delphi 6. At work we skipped from D5 to D2006 to D2009/10. So the code still very much reflects D5 days... – Marjan Venema Sep 10 '11 at 13:24

2 Answers2

0

When the simple server method is working, i think your problem has to be found i somethin the "real" code is doing or using.

It could be in the connection (try changing your simpler code to use the connection) Your problem can also be then CloneConnection. The Cloned connection is freed, when the connection it is cloned from is freed. See http://docwiki.embarcadero.com/VCL/en/SqlExpr.TSQLConnection.CloneConnection

BennyBechDk
  • 934
  • 7
  • 13
  • I've tried creating a TSQLConnection in the thread - and then assign the connection parameters (params) from the one used in the creation of the thread (input parameter). This produces the exact same error as having the connection cloned. – Bimmer_R Apr 08 '11 at 11:54
  • I've uploaded a complete test project here : https://forums.embarcadero.com/thread.jspa?threadID=52311&stqc=true – Bimmer_R Apr 08 '11 at 11:55
0

This has mostly been answered in the comments and the bug report, but... The problem you are seeing is caused by a multithreading issue in XE's marshaller code. If two threads (or two clients) call a server server method which takes in or return user defined types (any type which will use the marshaller/unmarshaller) at the same time, then an exception could happen.

I am unaware of a perfect workaround for XE, but if it is possible to not use user-defined types, then you shouldn't see multithreading issues.

Mat

Mat DeLong
  • 297
  • 4
  • 17
  • Hi Mat. Sure I can see that it only happens when using a user defined type. But that is one of the great forces of DataSnap as I see it. So basically its FAIL until they get it fixed? Looking at the structure of the program - I could rewrite the thread to use callback. So I would stop polling the server. But that really is just semantics. And would it help - I dont know. – Bimmer_R May 04 '11 at 11:48
  • In XE the marshaller code was not thread safe, which I agree was an unfortunate bug. Any solution which avoids using the marshaller (including callbacks) should avoid the issue. – Mat DeLong May 04 '11 at 14:20
  • "was not thread safe" ... sounds like you know a bit more - wanna share? :-) – Bimmer_R May 04 '11 at 21:12
  • I've tried several different things now. But going into callbacks is a bit tricky and faulty in XE - as long as we are using a tobject descendent. I've modified the channelcallback demo from embarcadero svn. And with the modified version it is possible to pass a tobject descendent in a callback. But not without going through the process of marshalling. QC#92921 is still "open" - and if it will be closed - I dont know. – Bimmer_R May 12 '11 at 20:36