About multithreading

Hi,

Few questions about writing multithreaded code... first, I vaguely
remember an old discussion about flaws in TThread implementation. I'm
using D5 - is there something to be said about it's TThread
implementation?

Second, what's the best thing to do when secondary thread waits for
data - to suspend it or to call Sleep(0)?

procedure TMyThread.Execute;
var DataQ: TQueue;
    cItm: PItem;
begin
  while(not Terminated)do begin
     DataQ:= FData.AcquireAccess;
     try
        if(DataQ.Count > 0)then cItm:= DataQ.Pop
        else cItm:= nil;
     finally
        FData.ReleaseAccess;
     end;
     //
     if(cItm <> nil)then begin
        // process item
     end else begin
        //Suspend;
        //Sleep(0);
     end;
  end;
end;


TIA
ain
0
ain
10/15/2008 2:00:48 PM
embarcadero.delphi.win32 2183 articles. 0 followers. Follow

5 Replies
1447 Views

Similar Articles

[PageSpeed] 30

ain valtin wrote:

> Few questions about writing multithreaded code... first, I vaguely
> remember an old discussion about flaws in TThread implementation. I'm
> using D5 - is there something to be said about it's TThread
> implementation?

The main thing to remember are:

 - do not use Suspend/Resume to control a thread, especially from
outside the thread. There is a race condition in the implementation of
these two functions in TThread that may cause the threads state to not
reflect reality. And suspending from outside is a receipe for
deadlocks, since you never know what the thread may currently be doing.
It may hold a lock on the memory manager, for instance...

 - when you want to terminate the thread, avoid waiting for it to
actually die. If you do that you may block the waiting thread forever.
The OS is perfectly capable of killing any secondary threads when the
main thread ends...

> Second, what's the best thing to do when secondary thread waits for
> data - to suspend it or to call Sleep(0)?

Neither. There should be a way to signal the thread to wake up when new
data arrives. The classic solution is to use a TSimpleEvent on which
the thread waits while it has no data to process. Adding data to its
work queue wakes up the thread, and you can also wake it up after
calling Terminate, to make sure it actually notices that it should
commit sepukku.



-- 
Peter Below (TeamB)  
Don't be a vampire (http://slash7.com/pages/vampires), 
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com
0
Peter
10/15/2008 6:01:36 PM
On Wed, 15 Oct 2008 11:01:36 -0700, Peter Below <none@nomail.please>
wrote:

>The main thing to remember are:
>
> - do not use Suspend/Resume to control a thread, especially from
>outside the thread. There is a race condition in the implementation of
>these two functions in TThread that may cause the threads state to not
>reflect reality. And suspending from outside is a receipe for
>deadlocks, since you never know what the thread may currently be doing.
>It may hold a lock on the memory manager, for instance...

I think I ran into this one on my first try... :(
Would it make sense to reimplement the TThread for myself? I would
like to make Synchronize to take an parameter (Pointer to user data)
and as ThreadWndProc's wParam is unused it seems to be easy to
implement... so I might as well fix the other problems with current
implementation.


> - when you want to terminate the thread, avoid waiting for it to
>actually die. If you do that you may block the waiting thread forever.
>The OS is perfectly capable of killing any secondary threads when the
>main thread ends...

Hmm, what exactly do you mean with "avoid waiting for it to actually
die"? I mean, when my main thread prepares data for the secondary
thread to be processed then at some point it must wait for the
secondary thread to complete it's work (ie "die")...?
Right now I'm using code like

procedure TImageEncoderThread.WaitTillQueueIsProcessed;
begin
  FDone:= True;
  FDataInEvent.SetEvent;
  WaitFor;
end;

procedure TImageEncoderThread.Execute;
var WorkQ: TQueue;
    cItm : PItem;
begin
  while(not Terminated)do begin
     WorkQ:= FWUIn.AcquireAccess;
     try
        if(WorkQ.Count > 0)then cItm:= WorkQ.Pop
        else cItm:= nil;
     finally
        FWUIn.ReleaseAccess;
     end;
     //
     if(cItm = nil)then begin
        if(FDone)then Break;
        FDataInEvent.ResetEvent;
        FDataInEvent.WaitFor(INFINITE);
     end else begin
        // process item...
     end;
  end;
end;

and call WaitTillQueueIsProcessed in the main thread to wait for the
secondary thread to finish it's job.


>> Second, what's the best thing to do when secondary thread waits for
>> data - to suspend it or to call Sleep(0)?
>
>Neither. There should be a way to signal the thread to wake up when new
>data arrives. The classic solution is to use a TSimpleEvent on which
>the thread waits while it has no data to process. Adding data to its
>work queue wakes up the thread, and you can also wake it up after
>calling Terminate, to make sure it actually notices that it should
>commit sepukku.

BTW what about threading in console app? I found that I have to call
Application.ProcessMessages in few situations or otherwise Synchronize
calls "won't get throught" and I have a deadlock (at least I quess
thats the case)... is this as expected for a console app or do I have
a problem in my code?

For example, to limit the amount of memory used I want to limit the
number of items in the in queue of the thread. I do it like

procedure TImageEncoderThread.AddItem(const aBMP: TBitmap);
var DataQ: TQueue;
    cItm: PItem;
begin
  New(cItm);
  cItm.BMP := aBMP;
  repeat
     DataQ:= FWUIn.AcquireAccess;
     try
        if(DataQ.Count < 20)then begin
           DataQ.Append(cItm);
           cItm:= nil;
        end;
     finally
        FWUIn.ReleaseAccess;
     end;
     if(cItm <> nil)then Application.ProcessMessages;
  until(cItm = nil);
  //
  FDataInEvent.SetEvent;
  if(Self.Suspended)then Self.Resume;
end;

and without the
     if(cItm <> nil)then Application.ProcessMessages;
line app will deadlock. I guess that's because without it Synchronize
called from Execute (to give processed data back to main thread) won't
return... is there a better way to deal with this stuff?


TIA
ain
0
ain
10/16/2008 2:07:50 PM
ain valtin wrote:

> > - do not use Suspend/Resume to control a thread, especially from
> > outside the thread. There is a race condition in the implementation
> > of these two functions in TThread that may cause the threads state
> > to not reflect reality. And suspending from outside is a receipe for
> > deadlocks, since you never know what the thread may currently be
> > doing.  It may hold a lock on the memory manager, for instance...
> 
> I think I ran into this one on my first try... :(
> Would it make sense to reimplement the TThread for myself? I would
> like to make Synchronize to take an parameter (Pointer to user data)
> and as ThreadWndProc's wParam is unused it seems to be easy to
> implement... so I might as well fix the other problems with current
> implementation.

What would you want to change? The Suspend/Resume problem is basically
a windows issue, since there is no way to check the actual state of a
thread from outside. I have used TThread descendents since D2 and never
had a problem with them after forgetting that Suspend/Resume do exist
<g>.

> > - when you want to terminate the thread, avoid waiting for it to
> > actually die. If you do that you may block the waiting thread
> > forever.  The OS is perfectly capable of killing any secondary
> > threads when the main thread ends...
> 
> Hmm, what exactly do you mean with "avoid waiting for it to actually
> die"? 

Calling the threads WaitFor method, or calling Free on a thread that
has not terminated yet (since the destructor calls WaitFor).

> I mean, when my main thread prepares data for the secondary
> thread to be processed then at some point it must wait for the
> secondary thread to complete it's work (ie "die")...?
> Right now I'm using code like
> 
> procedure TImageEncoderThread.WaitTillQueueIsProcessed;
> begin
>   FDone:= True;
>   FDataInEvent.SetEvent;
>   WaitFor;
> end;

That is OK if you use "throw away" threads. The problem is waiting for
a thread to die as part of the program shutdown. But your method still
has two major problems:

  - it will hang the main thread if the task never ends.
  - it will block message processing in the main thread, so windows
will not update when necessary, and XP will consider your
    program hung when this condition persists for more than 5 seconds
or so.

If you need to wait do it using MsgWaitForMultipleObjects, that allows
the waiting thread to wake up when a message arrives and process the
message. If possible, however, do not wait. Instead use the threads
OnTerminate event to get notified when it is done, or, if you use a
proper worker thread with a work queue (such a thread will not die when
the queue is empty, but wait until new data arrives), use a custom
event fired via a Synchronized method to get notified, or use a custom
message send to a receiver window to get notified.
 
> >> Second, what's the best thing to do when secondary thread waits for
> >> data - to suspend it or to call Sleep(0)?
> > 
> > Neither. There should be a way to signal the thread to wake up when
> > new data arrives. The classic solution is to use a TSimpleEvent on
> > which the thread waits while it has no data to process. Adding data
> > to its work queue wakes up the thread, and you can also wake it up
> > after calling Terminate, to make sure it actually notices that it
> > should commit sepukku.
> 
> BTW what about threading in console app?

Makes no sense unless you need to actually process user input in the
console. A console process is much more "linear" in execution than a
windowed GUI process. The console window is also owned by a different
thread (OS managed), so it will update if you call Write/WriteLn from
your work code without any additional effort on your part.
Application.ProcessMessages should *never* be called in a console app,
and neither should you use the Forms unit or Controls in a console app.

Synchonize does not work in a console app, since, by default, the apps
main thread has no Application object, no message loop, and thus no
place to process the custom messages send by Synchronize. As said above
you actually don't need it. Do not write a console app like you would
write a VCL GUI app, the processing structure is rather different.

If you prefer or have to do work in a secondary thread in a console app
the main thread needs to wait on appropriate objects, e.g. the thread
handles. There is also a special event object (SyncEvent) that is
signalled by Synchronize and which you can add to the list of handles
to wait on. When that event object becomes signalled you call the
CheckSynchronize method to get any pending Synchronized calls to
execute. I don't remember whether D6 already had this infrastructure in
place, by the way.

Synchronize also has a problem when used inside a DLL (same reason as
above: no Application window). The solution for that problem can be
found here: 21148: D6DLLSynchronizer for Delphi 6 and 7
http://cc.codegear.com/item/21148. But it requires the host apps main
thread to have a working message loop.



-- 
Peter Below (TeamB)  
Don't be a vampire (http://slash7.com/pages/vampires), 
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com
0
Peter
10/16/2008 5:43:42 PM
On Thu, 16 Oct 2008 10:43:42 -0700, Peter Below <none@nomail.please>
wrote:

>> Would it make sense to reimplement the TThread for myself?
>
>What would you want to change? The Suspend/Resume problem is basically
>a windows issue, since there is no way to check the actual state of a
>thread from outside.

Oh, OK then. I thought that perhaps it's a problem in the Borland's
implementation of the TThread and can be fixed...


>> Right now I'm using code like
>> 
>> procedure TImageEncoderThread.WaitTillQueueIsProcessed;
>> begin
>>   FDone:= True;
>>   FDataInEvent.SetEvent;
>>   WaitFor;
>> end;
>
>That is OK if you use "throw away" threads. The problem is waiting for
>a thread to die as part of the program shutdown. But your method still
>has two major problems:
>
>  - it will hang the main thread if the task never ends.

In my case, I'm pretty sure it ends - I call the method after I have
finished inserting data into it's "input queue" - so once the queue is
empty it sees that Done flag is set and dies.
Anyway, I now use MsgWaitForMultipleObjects() as you suggested...


>If you need to wait do it using MsgWaitForMultipleObjects, that allows
>the waiting thread to wake up when a message arrives and process the
>message.

So MsgWaitForMultipleObjects is the way to go even in console app?


>> BTW what about threading in console app?
>
>Makes no sense unless you need to actually process user input in the
>console. A console process is much more "linear" in execution than a
>windowed GUI process. The console window is also owned by a different
>thread (OS managed), so it will update if you call Write/WriteLn from
>your work code without any additional effort on your part.

I'm not sure I get what you mean...
I'm writing it as a console app because there really isn't any GUI -
sure I could create one, but it just makes sense to write it as
console app. It spits out (using Write/WriteLn) some info about it's
progress, but there isn't any input from user...


>If you prefer or have to do work in a secondary thread in a console app
>the main thread needs to wait on appropriate objects, e.g. the thread
>handles.

Just to make sure - my quick test seems to indicate that I have to use
Thread.ThreadID not Thread.Handle as parameter for
MsgWaitForMultipleObjects() - is that right?


> There is also a special event object (SyncEvent) that is
>signalled by Synchronize and which you can add to the list of handles
>to wait on. When that event object becomes signalled you call the
>CheckSynchronize method to get any pending Synchronized calls to
>execute. I don't remember whether D6 already had this infrastructure in
>place, by the way.

Well, even if has, I'm out of luck as I'm using D5 and that sure
doesn't have it :(


TIA
ain
0
ain
10/17/2008 4:26:29 PM
ain valtin wrote:

> > If you need to wait do it using MsgWaitForMultipleObjects, that
> > allows the waiting thread to wake up when a message arrives and
> > process the message.
> 
> So MsgWaitForMultipleObjects is the way to go even in console app?

It certainly does no harm using it. It may not be actually needed, but
whether it is needed depends on what you use in you application. If COM
is involved, for example, it may be needed since COM uses messages for
inter-thread communication.

> > Makes no sense unless you need to actually process user input in the
> > console. A console process is much more "linear" in execution than a
> > windowed GUI process. The console window is also owned by a
> > different thread (OS managed), so it will update if you call
> > Write/WriteLn from your work code without any additional effort on
> > your part.
> 
> I'm not sure I get what you mean...
> I'm writing it as a console app because there really isn't any GUI -
> sure I could create one, but it just makes sense to write it as
> console app. It spits out (using Write/WriteLn) some info about it's
> progress, but there isn't any input from user...

Then you do not need to do the work in a secondary thread, just do it
in the main thread.


> > If you prefer or have to do work in a secondary thread in a console
> > app the main thread needs to wait on appropriate objects, e.g. the
> > thread handles.
> 
> Just to make sure - my quick test seems to indicate that I have to use
> Thread.ThreadID not Thread.Handle as parameter for
> MsgWaitForMultipleObjects() - is that right?

No, the wait functions need handles, not thread IDs.


> > There is also a special event object (SyncEvent) that is
> > signalled by Synchronize and which you can add to the list of
> > handles to wait on. When that event object becomes signalled you
> > call the CheckSynchronize method to get any pending Synchronized
> > calls to execute. I don't remember whether D6 already had this
> > infrastructure in place, by the way.
> 
> Well, even if has, I'm out of luck as I'm using D5 and that sure
> doesn't have it :(

Then do not use Synchronize in your console application.

-- 
Peter Below (TeamB)  
Don't be a vampire (http://slash7.com/pages/vampires), 
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com
0
Peter
10/17/2008 5:06:35 PM
Reply:

Similar Artilces:

Understanding multithreaded applications in Delphi and Win32
Hi Guys, I'm building a multi-threading framework in D2009 that will be generic, easy-to-use and abstracts away much of the ugly details of threads and plan on releasing it as an open-source package. The framework is not universal in the sense that it does not solve all multi-threading problems, rather it focuses on the reader/writer problem where you have several threads creating messages and several threads reading these messages. The framework contains a thread manager that orchestrates the work, a thread-safe multi-read queue (unlike TThreadList) and a garbagecollector that mana...

Converting Delphi for Win32 to Delphi .Net(Prism)
Hi, I am currently migrating a project from Delphi for Win32 to Delphi.net. Part of my code currently goes into a directory and pulls out a random file from this directory and loads the contents of the file for me. This code doesn't seem to work in Delphi.Net. It uses PString and a number of functions in SysUtils that don't seem to be present in Delphi.net's SysUtils file. If anyone can help me please, it would be greatly appreciated! Many thanks, Jonathan Mackey Jonathan Mackey a écrit : > I am currently migrating a project from Delphi for Win32 to &...

Delphi.NET loading Delphi.Win32 Driver
Hi, What I'm trying to do is marshal an array of cardinal (or integer) back into managed memory from a win32 dll. I know how to pass managed memory into a win32 dll {code} var aa : array of Integer; Buffer : IntPtr; begin SetLength(aa,2); aa[0] := 1; aa[1] := 80; if not Supports(ExtractFilePath(Application.ExeName)+'Win32_Library\SDK_Driver.Win32.io', TypeOf(IMyFunctions), MyFunctions) then Exit; //loads the driver into memory. MyFunctions contains the method names found in the SDK_Driver. Buffer := Marshal.AllocHGlobal(2 * {Marshal.SystemDefaultC...

delphi Win32 using delphi .NET dll
Hi, I'm trying to use a delphi.NET dll in delphi.WIN32. I am currently using CodeGear Delphi 2007 with version2(base version) of .NET I can get the dll to import into the WIN32 application the only problem is when i include things such as: "using Classes,DateUtils, SysUtils" in the .NET dll the win32 application will instantly hang when any of the dll functions are called. Any help would be great thanks. Also I have tried this example and it also crashes for me? http://cc.embarcadero.com/Item/22688 -Braden I also found this.. "The problem is that, wehn you instal...

Migrate from Delphi 2007 for Win32 to Delphi XE
we use Delphi 2007 for Win32 to support legacy (32Bit) OWL-based pascal applications (yes i know it was a mistake not to switch to VCL 15 years ago). could our applications still be opened and compiled with Delphi XE? The existing projects are all plain Pascal-Code, coming back from the times of Turbo Pascal for Windows and later on Borland Pascal. Are there any improvements we could profit from (i.e IDE, Debugger)? Thanks Andrej > {quote:title=Andrej Dimic wrote:}{quote} > could our applications still be opened and compiled with Delphi XE? I'm not sure, but I guess ...

Win32 program: Delphi 7 vs Delphi XE5
How is a D7 Win32 program compared to a Delphi XE5 one in terms of stability and performance? Is Delphi XE5 good enough for a big ERP project with several DLL's and hundreds of units and forms? Thanks in advance Am 26.12.2013 15:02, schrieb lior ilan: > How is a D7 Win32 program compared to a Delphi XE5 one in terms of stability > and performance? > Is Delphi XE5 good enough for a big ERP project with several DLL's and > hundreds of units and forms? > Thanks in advance > Hello, XE5 has increased functionality. Stability seems to be ok for most ...

Win32 Delphi language features introduced since Delphi 7
Hi, Am I right in thinking that the language features introduced since Delphi 7 fall into the categories: a) language features dictated by .Net compatibility. e.g. Namespaces, Inlining, records with methods, operator overloading, pure interfaces, generics, extended RTTI and reflection; b) Unicode strings and supporting procedures? c) 64-bit support What other language features, if any, have been introduced since D7? Had most of the post-D7 languages features, except for generics, Unicode strings, and 64-bit support, been introduced in or before Delphi 2005? How bug-free were ...

[PATCH] Move Win32 from win32/ext/Win32 to ext/Win32
To compile the Win32 module under both "MSWin32" and "cygwin" the module needs to be moved to the ext/ subdirectory. To preserve the change history this should be done with `p4 integrate`: p4 integrate win32/ext/Win32/... ext/Win32/... p4 delete win32/ext/Win32/... The attached patch then updates Configure to build the module under cygwin only, and updates the MANIFEST. The MSWin32 builds will continue to find the module automatically using the FindExt.pm module. Cheers, -Jan diff -dur bleadperl/Configure bleadnew/Configure --- bleadperl/Conf...

re: [PATCH] Move Win32::* functions from win32/win32.c to ext/Win32/Win32.xs
----=_e3pon25ktlh8mqd3tgjj1tt7en166ucrfq.MFSBCHJLHS Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit > patch2.diff moves the Win32::* functions into the ext/Win32 module and > adds forwarder functions to load the module at runtime on first use: > > static void > forward(pTHX_ const char *function) > { > dXSARGS; > Perl_load_module(aTHX_ PERL_LOADMOD_NOIMPORT, newSVpvn("Win32",5), NULL); > PUSHMARK(SP-items); > call_pv(function, GIMME_V); > } It just occurred to me that Perl_load_modul...

Delphi Win32 Inkoverlay
I need to use Inkoverlay for 4 entry fields. Do I need to use 4 separate inkoverlay components? What is the best way to create these in code? I need to know this because sometimes the number of entries can vary from 4 to 10. ...

Delphi (Win32)
Hi all, I am looking for a Delphi/Win32 equivalent of the .Net SqlBulkCopy class... is there something available? Background: I want to load CSV/Text files directly into a DB table. Files have various formats, e.g. field separator, text delimiter etc. TIA, Levend. > Background: I want to load CSV/Text files directly into a DB table. Files have various formats, e.g. field separator, text delimiter etc. What database are you using ? Hi Robert, I am mainly using MS SQL Server. Thus a 1:1 equivalent of .Net SqlBulkCopy is what I am looking for - although I would not complain...

SA Delphi Prism and not Win32??
Hi I have an active SA for RAD Studio Architect. Today I received notify for my serial of Delphi Prims 2010. But I not received my serial for the Delphi 2010 Win32! Best Regards __________ Information from ESET NOD32 Antivirus, version of virus signature database 4371 (20090826) __________ The message was checked by ESET NOD32 Antivirus. http://www.eset.com Hi, Is there anyone else with the same problem? I'm still waiting... When can I get my serial for Delphi 2010 Win32? But the process of notify for Delphi Prism 2010 and Delphi 2010 Win32 is the sam...

Delphi and Delphi for .Net
It seems that Delphi for .Net is slower than Delphi Win32 native applicaiton. I would like to know is it true all .Net application is slower than Win32 native applicaiton or it is Delphi for .Net only. Your information is great appreciated, Inung On 2011-06-21 18:20:17 +0100, Inung Huang said: > It seems that Delphi for .Net is slower than Delphi Win32 native applicaiton. > I would like to know is it true all .Net application is slower than > Win32 native applicaiton or it is Delphi for .Net only. If you are only running the code in the application once then, yes, yo...

turbo delphi for win32 and C++
A few weeks ago, I downloaded (free) turbo delphi for win32. I believe it had Turbo C++ with it, but I am not sure. In the AddRemove Program, it just lists Borland Turbo Delphi. No memtion of Turbo C++. How can I tell what I have? If I don't have the Turbo C++, how can I get it. Thanks! -- ô¿ô V e r n Vern Marsden wrote: > A few weeks ago, I downloaded (free) turbo delphi for win32. > I believe it had Turbo C++ with it, but I am not sure. You believe wrong. The "Turbo" products only have one personality, and only one can be inst...

Delphi Win32 applications on iPad?
Hello, I'm not even sure if this is possible but I thought i would ask anyway. I have developed a native Win32 application that runs in Windows using Delphi 2006 and I have been asked by my company to see about the possibility for the application to run on Apple iPad's. Can anyone offer any advice or point me in a direction? From a quick google search the Delphi Prism XE IDE came up quite alot? can Delphi Prism XE open up and port my win32 code? Chris > > Can anyone offer any advice or point me in a direction? From a quick google search the Delphi Prism XE IDE ca...

Web resources about - About multithreading - embarcadero.delphi.win32

Multithreading (computer architecture) - Wikipedia, the free encyclopedia
Multithreading computers have hardware support to efficiently execute multiple threads . These are distinguished from multiprocessing systems ...

Introduction to Multithreading, Superthreading and Hyperthreading
We took some time to look into simultaneous multithreading (SMT), as hyper- …

AMD’s next-gen CPU leak: 14nm, simultaneous multithreading, and DDR4 support
New rumors are starting to surface regarding AMD's upcoming Zen architecture. Will 14nm technology, DDR4 support, and a new multi-threading model ...

JXCore brings multithreading, sort of, to Node.js
Node.js's open source nature and highly liberal MIT licensing same as the V8 JavaScript engine at the heart of Node.js invites all manner of ...

If you have multithreading in Windows Phone 7 do you need multitasking?
Exploring how WP7 handles multiple application threads

Determinism Is Not Enough: Making Parallel Programs Reliable with Stable Multithreading - Lambda the ...
Junfeng Yang, Heming Cui, Jingyue Wu, Yang Tang, and Gang Hu, "Determinism Is Not Enough: Making Parallel Programs Reliable with Stable Multithreading" ...

Stack Overflow
Stack Exchange log in - careers - chat - meta - about - faq Questions Tags Users Badges Unanswered Ask Question Top Questions interesting 325 ...

Central processing unit - Wikipedia, the free encyclopedia
The central processing unit ( CPU ) is the portion of a computer system that carries out the instructions of a computer program , to perform ...

AK's weblog - Entries tagged as benchmark
Well, be warned that this comparison is highly unscientific and makes no claims about the overall performance of the mentioned webservers. Anyway, ...

MPlayer OSX Extended
is the future of MPlayer OSX. Leveraging the power of the MPlayer and FFmpeg open source projects, aims to deliver a powerful, functional and ...

Resources last updated: 11/23/2015 7:57:54 PM