4
votes

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

2
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
@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
You shouldn't call Randomize in the constructor. Only one call to Randomize during the startup of your application will do.Uwe Raabe
@Marjan Venema Interesting, I usually just initialize the thread instance data before calling inherited constructor, not suspended.Ondrej Kelle
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

2 Answers

0
votes

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

0
votes

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