Cannot initialize streamer that is already in use

delphi

(marcantheunis) #1

since the latest RODA release i’m getting the above message when invoking the code below in the RO server
the RO service that calls this code is a

fClassFactory_ParameterService := TROPooledClassFactory.Create('ParameterService', {$IFDEF FPC}@{$ENDIF}Create_ParameterService, TParameterService_Invoker, 5,
                               pbCreateAdditional,True);

there is a saveall method in this service that does the following:

  try
    FCritical.Acquire;
    Result := False;

    if FServiceEnabled and FLoaded then
    begin
      try
        FFactory.CreateInstance(LocalMessage.ClientID, FInstance);

        if Supports(FInstance, IROObjectActivation, FObjActivation) then
          FObjActivation.OnActivate(LocalMessage.ClientID, LocalMessage);

        // We have the object now. We can query for any interfaces it might support
        // IDARemoteService is more generic in this case, but we could have asked for
        // INewService as well and it would have worked the same way
        FreeAndNil(FStream);
        FStream := Binary.Create;
        LocalAdapter.WriteDelta(FStream,LocalDataTable);

        if Supports(FInstance, IDARemoteService, FObjRemoteDAService) then
          FStreamDelta := FObjRemoteDAService.UpdateData(FStream)
        else
        begin
          MessageDlg('DataAbstract interface not supported',mtError,[mbOk],0);
          raise Exception.Create('DataAbstract interface not supported');
        end;

        LocalAdapter.Initialize(FStreamDelta,aiReadFromBeginning);
        LocalDataTable.Delta.Clear;
        LocalAdapter.ReadDelta(LocalDataTable);
        LocalAdapter.Finalize;

        // Invokes the Deactivation event chain
        if Supports(FInstance,IRoObjectActivation,FObjActivation) then
          FObjActivation.OnDeactivate(LocalMessage.ClientID);

        Result := True;
      finally
        FFactory.Releaseinstance(LocalMessage.ClientID, FInstance);
        FreeAndNIL(FStream);
        FreeAndNil(FStreamDelta);
        FObjActivation := nil;
        FObjRemoteDAService := nil;
        FInstance := nil;
      end;
    end;
  finally
    FCritical.Release;
  end;

anything wrong in this code?
tia!


(EvgenyK) #2

put LocalAdapter.Finalize; into try/finally section like

        LocalAdapter.Initialize(FStreamDelta,aiReadFromBeginning);
        try
           LocalDataTable.Delta.Clear;
           LocalAdapter.ReadDelta(LocalDataTable);
        finally
           LocalAdapter.Finalize;
        end;

or use overload version of ReadDelta, that does it for you:

           LocalDataTable.Delta.Clear;
           LocalAdapter.ReadDelta(FStreamDelta, LocalDataTable);

btw, we have LocalDataAdapter that makes above things mush easily. usage the same as RemoteDataAdapter, i.e.

LocalDataTable.ApplyUpdate;

(marcantheunis) #3

it’s ‘legacy’ code :slight_smile:
maybe i should alter it using the localdatatable component…


(marcantheunis) #4

ok, i have a problem i suppose:

in our DA3 schema and service_impl unit i have function calls that stream multiple datasets in 1 call
nothing fancy bout that
but when the database is out of sync with the data (schema) i expect then the server throws an error saying that column XXX is not found
so far so good
now when i restart the client app then i get the above error
the service itself is a pooledfactory one
so a crash in fetching data due to db changes results in stream errors when rebooting the client…

now this is a case i can probably solve by protecting the code that fails, but there are other circomstances that are not known, so is there a general approach to at least make sure that the running instance of this service (pooled) is cleaned up so that the streamer in use error does not occurs when restarting the client?
or is there another way to trap these errors…?


(EvgenyK) #5

errors on client-side like that column XXX is not found when server schema is differ in comparing with client schema can be solved very easily: just clear Table.Fields and open table. after this this table will use server-side schema.

can you give a bit more details about your issue?
if it contains some private info we can move to support@


(marcantheunis) #6

well it’s not the client that is missing a fielddef or has one to many (we do not have persistent fields at client side)
it is the actual database that was not altered
the schema included a column to retrieve (view in sql) and that view did not got updated
as a result the client side crashed cause the server reported the missing field
but when restarting the client i get the cannot initialize streamer that is already in use
probably due to the fact that the service is pooled and thus never destroyed
so the question is really how to elevate this problem in this pooled instance so that i don’t have to restart the appserver
secondly how to trap this at service level so i can determine other origins of this crash

i’ve rolled out an update of our soft with the latest roda release
we never encountered this error on such a scale
i do see a few reports during the year but nothing really disturbing
our latest release 40% of our customers encountered this problem
this was 0.1% due to the view that was not updated, but for the other 39.9% i don’t have any explanation
so there must be another call that gives the same problem

that said, i’ve tested this with our previous release (using build 107) and i could reproduce the same issue
so it does not directly has it’s origin in the latest release, although it surfaced exponentially now and i’m looking for the origin of the problem…


(EvgenyK) #7

the cannot initialize streamer that is already in use

this means that finalization of streamer wasn’t been done.
put that code in try/finally and it will solve issue with streamer

        LocalAdapter.Initialize(FStreamDelta,aiReadFromBeginning);
        try
           LocalDataTable.Delta.Clear;
           LocalAdapter.ReadDelta(LocalDataTable);
        finally
           LocalAdapter.Finalize;
        end;

(marcantheunis) #8

so no way of trapping this inside the service itself i guess?
i don’t see any event that i could use…


(EvgenyK) #9

you can override UpdateData method like

function TMyService.UpdateData(const Delta: Binary): Binary;
begin
  try
    result := inherited UpdateData(Delta);
  except 
      // do somethings
  end;
end;

(marcantheunis) #10

no overload method…? (binadapter)


(EvgenyK) #11

You can use this method:

procedure TDADataStreamer.ReadDelta(Stream: TStream;
  const Destination: IDADelta;
  DeltaName: string = '';
  ReadFromBeginning: boolean = TRUE);

(marcantheunis) #12

should be
LocalAdapter.ReadDelta(FStreamDelta, LocalDataTable.Delta);
i suppose


(marcantheunis) #13

using this i always get the streamer error…


(marcantheunis) #14

and offcourse it can’t since i’m initializing the streamer explicitely
nvmd…


(marcantheunis) #15

last question:

so i’de better alter the following code:

Result := Binary.Create;
ds := InternalGetDADataset(‘table’, StatementName, StatementSuffix);
DABINDataStreamer.Initialize(Result, aiwrite);
TotalRows := DABINDataStreamer.WriteDataset(ds, [woRows, woSchema], -1);
DABINDataStreamer.Finalize;

into this?:

Result := Binary.Create;
ds := InternalGetDADataset(‘table’, StatementName, StatementSuffix);
TotalRows := DABINDataStreamer.WriteDataset(Result, ds, [woRows, woSchema], -1);

yes?


(marcantheunis) #16

but doing so fails when combining multiple datasets in 1 roundtrip…


(EvgenyK) #17

you have to put DABINDataStreamer.Finalize; into try/finally section otherwise you will always receive Cannot initialize streamer that is already in use error if exception was raised between DABINDataStreamer.Initialize and DABINDataStreamer.Finalize at second call.

Replace this code:

DABINDataStreamer.Initialize(....);
...
DABINDataStreamer.Finalize;

with

DABINDataStreamer.Initialize(....);
try
  ...
finally
   DABINDataStreamer.Finalize;
end;

in all places inside your sources.


(marcantheunis) #18

yes i know
the question was if i’m using
DABINDataStreamer.WriteDataset( STREAM , ds, [woRows, woSchema], -1);

how i can write multiple datasets into the same stream
but since the above call does the initialize and finalize implicit i guess i have to stick to doing the initialize and finalize myselves like stated above and use the writedataset without the stream argument


(EvgenyK) #19

this method is designed for writing single dataset.
if you see into code, you can see

function TDADataStreamer.WriteDataset(Stream: TStream;
  const Source: IDADataset; Options: TDAWriteOptions;
  MaxRows: integer): integer;
begin
  Initialize(Stream, aiWrite);
  try
    result := WriteDataset(Source, Options, MaxRows);
  finally
    Finalize;
  end;
end;

in your case you can use next code:

streamer.Initialize(Stream, aiWrite);
try
  streamer.WriteDataset(table1, ...);
  streamer.WriteDataset(table2, ...);
..
  streamer.WriteDataset(tableN, ...);
finally
  streamer.Finalize;
end;

(marcantheunis) #20

yep ok!