You are not logged in.
Hi,
I've got a small app, based on the martin-doyle InterfacedBasedServices demo.
Here are some pertinent code snippets:
TNotebook = packed record
ID: TID;
Title: string;
end;
TNotebookArray = array of TNotebook;
INotebookService = interface(IInvokable)
['{644FEAD9-5DCC-4C61-9740-C6FE8AD2CDAB}']
function Add(var Notebook: TNotebook): TServiceCustomStatus;
function Get(const NotebookId: TID; var Notebook: TNotebook): TServiceCustomStatus;
function List(var Notebooks: TNotebookArray): TServiceCustomStatus;
function Update(const Notebook: TNotebook): TServiceCustomStatus;
function Delete(const NotebookId: TID): TServiceCustomStatus;
end;
TOrmNotebook = class(TOrm)
private
FCreatedAt: TCreateTime;
FModifiedAt: TModTime;
FTitle: string;
FUserId: TSessionUserId;
public
published
property CreatedAt: TCreateTime read FCreatedAt write FCreatedAt;
property ModifiedAt: TModTime read FModifiedAt write FModifiedAt;
property Title: string read FTitle write FTitle;
property UserId: TSessionUserId read FUserId write FUserId;
end;
function TNotebookService.Update(const Notebook: TNotebook): TServiceCustomStatus;
var
OrmNotebook: TOrmNotebook;
begin
OrmNotebook := TOrmNoteBook.Create;
try
if Self.Server.Orm.Retrieve(Notebook.ID, OrmNoteBook, True) then
begin
OrmNotebook.Title := Notebook.Title;
Self.Server.Orm.Update(OrmNotebook);
Result := HTTP_SUCCESS;
end
else
begin
Result := HTTP_NOTFOUND;
end;
finally
OrmNotebook.Free;
end;
end;
constructor TSampleServer.Create(aModel: TOrmModel; const aDBFileName:
TFileName);
begin
inherited Create(AModel, ADBFileName, True);
ServiceDefine(TNotebookService, [INotebookService], sicShared);
...
end;
The first time the client is calling NotebookService.Update, Orm.Retrieve returns true, so the update code executes. The second time it is called (a few seconds later), Orm.Retrieve returns false, so the result is HTTP_NOTFOUND. However, the input parameter, i.e. Notebook.ID is the same in both cases, so I don't understand why Orm.Retrieve returns false.
Any ideas why this is happening?
(The client is a vcl TRestHttpClient)
Offline
Log when update called the first time:
20240421 11111121 ! + mormot.soa.client.TServiceFactoryClient(02a53968).InternalInvoke INotebookService.Update({ID:9,Title:"General"})
20240421 11111121 ! + mormot.rest.http.client.TRestHttpClientSocket(029d52c0).InternalUri POST
20240421 11111121 ! trace mormot.rest.http.client.TRestHttpClientSocket(029d52c0) InternalRequest POST calling THttpClientSocket(029bbfb0).Request
20240421 11111121 " + server.TSampleServer(029fc120).URI POST root/NotebookService.Update?session_signature=0009b4c900274a19f9436761 in=24 B
20240421 11111121 " call server.TSampleServer(029fc120) NotebookService.Update[{ID:9,Title:"General"}]
20240421 11111121 " DB server.TSampleServer(029fc120) prepared 258us mORMotNotesVCL.db SELECT ID,CreatedAt,ModifiedAt,Title,UserId FROM Notebook WHERE RowID=? [{id:2,detail:"SEARCH Notebook USING INTEGER PRIMARY KEY (rowid=?)"}]
20240421 11111121 " SQL server.TSampleServer(029fc120) 492us id=9 SELECT ID,CreatedAt,ModifiedAt,Title,UserId FROM Notebook WHERE RowID=?
20240421 11111121 " cache mormot.db.raw.sqlite3.TSqlDatabase(02a53628) mORMotNotesVCL.db cache flushed
20240421 11111121 " DB server.TSampleServer(029fc120) prepared 151us mORMotNotesVCL.db UPDATE Notebook SET ModifiedAt=?,Title=?,UserId=? WHERE RowID=? [{id:4,detail:"SEARCH Notebook USING INTEGER PRIMARY KEY (rowid=?)"}]
20240421 11111121 " SQL server.TSampleServer(029fc120) 4.22ms UPDATE Notebook SET ModifiedAt=:(135843590859):,Title=:('General'):,UserId=:(3): WHERE RowID=:(9):
20240421 11111121 " srvr User Interface POST root/NotebookService.Update=200 out=16 B in 5.85ms
20240421 11111121 " ret mormot.rest.server.TRestServerRoutingRest(032c4e70) {"result":[200]}
20240421 11111121 " - 00.005.985
20240421 11111121 ! clnt mormot.rest.http.client.TRestHttpClientSocket(029d52c0) POST root/NotebookService.Update?session_signature=0009b4c900274a19f9436761 status=200 len=16 state=0
20240421 11111121 ! - 00.007.824
20240421 11111121 ! ret mormot.soa.client.TServiceFactoryClient(02a53968) {"result":[200]}
20240421 11111121 ! - 00.008.093
Log when called the second time:
20240421 11112003 ! + mormot.soa.client.TServiceFactoryClient(02a53968).InternalInvoke INotebookService.Update({ID:9,Title:"Generalxxx"})
20240421 11112003 ! + mormot.rest.http.client.TRestHttpClientSocket(029d52c0).InternalUri POST
20240421 11112003 ! trace mormot.rest.http.client.TRestHttpClientSocket(029d52c0) InternalRequest POST calling THttpClientSocket(029bbfb0).Request
20240421 11112003 % + server.TSampleServer(029fc120).URI POST root/NotebookService.Update?session_signature=0009b4c900274a3b461dac76 in=27 B
20240421 11112003 % call server.TSampleServer(029fc120) NotebookService.Update[{ID:9,Title:"Generalxxx"}]
20240421 11112003 % srvr User Interface POST root/NotebookService.Update=404 out=16 B in 210us
20240421 11112003 % ret mormot.rest.server.TRestServerRoutingRest(032c5170) {"result":[404]}
20240421 11112003 % - 00.000.320
20240421 11112003 ! clnt mormot.rest.http.client.TRestHttpClientSocket(029d52c0) POST root/NotebookService.Update?session_signature=0009b4c900274a3b461dac76 status=404 len=16 state=0
20240421 11112003 ! - 00.003.037
20240421 11112003 ! trace mormot.rest.http.client.TRestHttpClientSocket(029d52c0) POST root/NotebookService.Update returned 404 (Not Found) with message {"result":[404]}
20240421 11112003 ! ret mormot.soa.client.TServiceFactoryClient(02a53968) {"result":[404]}
20240421 11112003 ! - 00.003.342
On second call, it's not that the db query fails, it's that the db layer is not being called.
I have prepared a sample project:
https://www.dropbox.com/scl/fi/8s8axgdc … sfbrl&dl=0
It's a delphi project.
Steps to reproduce:
1. Press Connect
2. Type in notebook title edit, then press Add, to add an entry. (e.g. General)
3. Click on the entry in the list box (this fills the notebook edit)
4. Modify the title. (e.g. Generalxxx)
5. Press Edit, this works as expected
repeat 3 and 4. This time it shows message "Error updating.. " etc.
Offline
With minimal debugging, we can see that the record is LOCKed so Update() fails.
As stated by the Retrieve() documentation:
// - if ForUpdate is true, the REST method is LOCK and not GET: it tries to lock
// the corresponding record, then retrieve its content; caller has to call
// UnLock() method after Value usage, to release the record
You did not call UnLock().
So ForUpdate should be left to its default false value.
Two remarks:
- use a separated TRestServerDB for the persistence, and a TRestServerFullMemory for the services publication;
- use a lock to protect the TRestServerDB process if you make some nested ORM calls like Retrieve + Update, otherwise it may not be thread safe.
But thanks a lot for creating a minimal sample, to make it easy to guess the problem.
Offline
Thanks a lot for the info. I'm going to be away for a few days. I will follow up when I return...
Offline
I was curious to know what "minimal debugging" you had done?
Looking at the source, I could see a number of nested classes, and "method redirection". So I think it would take considerable time to gain some familiarity with that part of the code. Hence, I'm curious if you have some tips to bypass that, as a first step ..
Offline
For instance:
- Identify the interface type definition;
- Look at the comments of the used method (e.g. Retrieve);
- Adapt the code to follow what is described here (e.g. "if ForUpdate is true, call Unlock to release the record").
We try to make the comments always accurate and up-to-date. It is the actual documentation.
If you find something wrong, please feel free to report.
Offline
Thanks. I was looking for the right class instead of looking for the interface.
Offline
- use a separated TRestServerDB for the persistence, and a TRestServerFullMemory for the services publication;
1) With the current implementation, the service is inheriting from TInjectableObjectRest, and so has convenient and automatic access to the Server/DB instance. If there are 2 separate servers, how can I access the db server from the service method implementation, which is being defined for the memory server. i.e in TNotebookService.Update, Self.Server would be the TRestServerFullMemory. How to access a separate TRestServerDB instance?
2) Just to clarify, why you make this recommendation. I can see that in a more complex situation, it will be a good idea/more flexible setup. But in a simple "CRUD app" type of situation, where each server call is going to essentially make a call to the db layer, is there going to be an advantage to this setup?
Thanks
Offline
How to access a separate TRestServerDB instance?
You can find posts on this topic in this forum.
type
TCustomServiceObject = class(TInjectableObjectRest, ICustomService)
protected
function InternalAdd(const pmcItem: TOrmCustomRecord; pmForceID: Boolean = False): TID;
public
function GetDBDataRestServer: TRestServerDB; overload;
function GetDBBlobRestServer: TRestServerDB; overload;
end;
function TCustomServiceObject.InternalAdd(const pmcItem: TOrmCustomRecord; pmForceID: Boolean): TID;
var
restServer: TRestServerDB;
begin
restServer := GetDBDataRestServer;
if (restServer <> Nil) and (pmcItem <> Nil) then
Result := restServer.Server.Add(pmcItem, True, pmForceID)
else
Result := -1;
end;
function TCustomServiceObject.GetDBDataRestServer: TRestServerDB;
begin
with TRestAdminServer(Server) do
Result := RestServerPool.FindRestServer(GetDBDataRestServerID(ServiceRunningContext.Request.Session));
end;
function TCustomServiceObject.GetDBBlobRestServer: TRestServerDB;
begin
with TRestAdminServer(Server) do
Result := RestServerPool.FindRestServer(GetDBBlobRestServerID(ServiceRunningContext.Request.Session));
end;
With the technique shown above, you can also manage several separate databases for each organization without any problems. If you only want to use several databases, you do not need a pool and can access them directly. Then the following is enough:
type
TServerMain = class(TSynPersistent)
private
FHttpServer: TVGSHttpServer;
FDBDataRestServer: TDBDataRestServer;
FDBBlobRestServer: TDBBlobRestServer;
With best regards
Thomas
Offline