Jump to content

aehimself

Members
  • Content Count

    1053
  • Joined

  • Last visited

  • Days Won

    23

Everything posted by aehimself

  1. Maybe trial comes with compiled .DCUs only? Can you find Vcl.Controls.dcu?
  2. Connecting - 15 seconds. Application stops responding. Opening a large dataset with lots of BLOB fields - 1 hour. Application still does not respond. Make a change, post it back and commit. Another 5 seconds of white screen. But noone really cares at this point. For ease of code, put everything on your form. For everything else, there are Threads. (powered by Loreal and Mastercard 😄)
  3. That is actually (half of) the best thing(s) that can happen to an application. The other half is, having dataset operations (.Open, .Post, .Refresh, etc.) in the same thread.
  4. aehimself

    Database app good practice

    I started actively using databases in the past couple of years in my applications, and these are the things I wished to know from the beginning: - Every SQL thing should be in a separate thread. If connection takes 20 seconds or you are downloading a very large dataset over a slow connection, your application will be frozen. Publish important data or the complete dataset objects as properties. Just make sure you disconnect all datasources before any operation and reconnect them after, as data events will start to fire off in an inconsistent state causing AVs most probably. - When it comes to threads, a golden rule is that each thread should have it's own connection to the database. You also want to make sure that threads are working with different tables or you should have correct transaction isolation set up. - For service applications I wrote my own "ORM", which is basically a wrapper for a TQuery. Each field the query returns are published as typed properties. So instead of query.Edit; query.FieldByName('field').AsString := 'Hello, world'; query.Post; I simply can say: myTable.Field := 'Hello, world'; and myTable takes care of everything else. I took this one step further after a couple of DB changes and wrote my own planner. I tell it what kind of tables and fields I want, and it generates MySQL and MSSQL create and delta scripts AND all the myTable classes as Delphi source files. I make a change, I have all the SQL scripts ready to ship with the update and I already can use the new fields in all of MyTable objects... you get the point. - Depending on the component you use (and how they access the databases) client libraries might not be thread safe or requiring special parameters upon establishing the connection to be thread safe! I found it as a best practice for example to have a critical section when connecting as I had strange access violations when 7 worker threads tried to connect using the same library at the same time. - If performance is critical, do not use TQuery.FieldByName. That's all I can think of for now but I'm sure I missed a few. If anything else pops up, I'll try to remember to update this thread.
  5. Start -> cmd -> dir C:\fileiwanttofind.now /s /b Works like a charm.
  6. aehimself

    Looping Memo.Text

    If you are willing to look into different components, Zeos has a TZSQLProcessor which does just this. You give it your full query, like "Create Table A (ID Integer); Insert into A Values(1); Update A set ID=2 Where ID=1;" Then it splits it up and executes them one by one. Other than this, you'll have to write the parser yourself. Beware though, splitting up simply by ; is NOT GOING TO WORK. Examples: INSERT INTO MYTABLE (MYSTRINGFIELD) VALUES (";") INSERT INTO MYTABLE (MYSTRINGFIELD) VALUES ("x") -- This is just for fun; DROP TABLE MYTABLE and so on, you get the point...
  7. aehimself

    TwSocket Udp Client how to receive Bytes Properly ?

    Just a small heads-up; binary transfer requires different handling than text. See
  8. aehimself

    fun coding challenge

    Just store the TMemos in a TList when you create them so you don't have to find them later. When generating; have two nested cycles going through that list. Outer from Low(list) To High(list) - 1; inner from outer var to High(list). Not efficient, but easy enough to understand what is going on.
  9. aehimself

    Drone control from mobile

    I guess it depends on the resolution and color depth, but isn't transferring still images and showing them as a video... laggy? I never worked with videos in my entire life, but - as far as I understood - this is why they identify key frames; so they transfer / store only the delta inbetween those.
  10. That's a lesson I learned through a number of painful refactorings. NEVER put business logic to UI controls. Like... Unit Helper; Interface Function MyFunction: String; Procedure DoSomething; Implementation Function MyFunction: String; Begin Result := 'Hello, world'; End; Procedure DoSomething; Begin Sleep(1000); End; End. And then just put Helper in the uses classes of your form, and simply call ShowMessage(MyFunction); ?
  11. aehimself

    Delphi 10.4.1 and the IDE FIx Pack

    And this is fine, unless a significant amount of them will never be looked at, and just forgotten when 10.4.2 comes out. Afaik, there are bugs what Delphi is carrying for more than 5 years.
  12. Hello, Most probably this is NOT going to be an issue with ICS as I experienced the very same symptom with TServerSocket before I made the switch. I'm mainly looking for tips on where can I start debugging the issue as for the time being I'm completely out of ideas. I have an application which is connecting to a server on a single, TCP socket. On average, 80 bytes (binary) are sent from the clients to the server each minute, in one packet. The TCP channel is unidirectional, messages are only going from the client to the server. Everything is working perfectly, until a seemingly random time; when for a seemingly random client the data is not received anymore. The TCP connection is still established, the client is still sending the packet and WireShark confirms that it arrives to the server machine. It seems that the socket's receive event stops firing off. What is even more interesting, that it affects random clients (with different OSes, sometimes Windows 2000, sometimes 2012 R2, sometimes 2019), only causes one client to get stuck at a time, but multiple clients can get stuck during the process. The application can remain in this state for days without memory increase (so I'm not inflating the local buffer endlessly, without triggering the data processing), memory or handle leaks. If I restart the client or the server, forcing the client to reconnect, everything jumps back to normal. As for a little background, the very same logic was working perfectly, when the binary data was converted to, and sent as text. By switching to binary the sent data size was reduced from 200-500 bytes to 60-100. I don't know why but I suspect this change triggered the error I'm seeing now; and maybe because of the data size. https://docs.microsoft.com/en-us/troubleshoot/windows/win32/data-segment-tcp-winsock mentions that TCP is not really efficient with unidirectional, small data packets but it only will result delivery delay. For me it seems to be irrelevant. Sending code looks something like this (TBufferLength = Word): Function TCommunicationEngine.Send(Const inText: String): Boolean; Var buf, len: TBytes; sent: Integer; Begin Result := False; Try // Step 1 - String to TBytes buf := TEncoding.UTF8.GetBytes(inText); // Step 2 - Encryption of "buf" // ... // If the buffer exceeds the maximum length allowed, raise an error as it can not be sent! If Length(buf) > TBufferLength.MaxValue Then Raise ETCPPortError.Create('Buffer overflow, cannot send ' + Length(buf).ToString + ' bytes!'); // Step 3 - Append the length of the buffer to the beginning of the buffer SetLength(len, SizeOf(TBufferLength)); PBufferLength(@len[0])^ := Length(buf); SetLength(buf, Length(buf) + Length(len)); Move(buf[0], buf[Length(len)], Length(buf) - Length(len)); Move(len[0], buf[0], Length(len)); // Step 4 - Send the completed buffer sent := _tcpport.Send(@buf[0], Length(buf)); // Step 5 - Post-sending verifications If sent < 1 Then Raise ETCPPortError.Create('No data was sent!'); Log(LOG_TCP, '> ' + BytesToString(buf) + ' (' + sent.ToString + ' bytes)'); Result := True; Except On E:Exception Do HandleException(E, 'while sending data'); End; End; Receiving block looks like this (TClientConnection is a descendant of TWSocketClient, _data is a strict private TBytes, _count is a strict private Integer): Procedure TClientConnection.ConnectionDataAvailable(inSender: TObject; inError: Word); Var buf: TBytes; need, len, read: Integer; debuglog: String; Begin // Note that due to how TCP works, if packets are arriving at high speed they might be appended to one single ReceiveText event. If BanList.IsBanned(Self.PeerAddr) Then Self.Close // If the IP where the data is coming from is banned, disconnect Else Begin len := Self.RcvdCount; If len = 0 Then Exit; Repeat debuglog := Self.PeerAddr + ' > Read cycle starts. received data size: ' + len.ToString + ', socket data size: ' + Length(_data).ToString + ', position: ' + _pos.ToString + '. '; If _pos = 0 Then Begin // Position is 0 = there is no fragment. Read the data size first If len < SizeOf(Word) Then Begin BanList.Failed(Self.PeerAddr, 'Packet size is incorrect'); Self.Close; // Packet is corrupted, reset the connection Exit; End; SetLength(buf, SizeOf(TBufferLength)); Self.Receive(@buf[0], Length(buf)); // buf now contains the data size. Resize socket's data length SetLength(_data, PBufferLength(@buf[0])^); // As the data size is read out, reduce the received length len := len - Length(buf); debuglog := debuglog + 'Prepared a ' + Length(_data).ToString + ' byte buffer. '; End; need := Length(_data) - _pos; If need < 0 Then Begin // this should never happen. I'll just keep it here for debugging purposes... Log(LOG_STD, 'Possible memory corruption happened. Data size of ' + Self.PeerAddr + ' is ' + Length(_data).ToString + ', position is ' + _pos.ToString); Self.Close; Exit; End Else If need > 0 Then Begin If len < need Then SetLength(buf, len) // If we received less bytes than needed to fill the buffer, read everything Else SetLength(buf, need); // If we received more bytes than needed to fill th buffer, only read what is needed debuglog := debuglog + 'Reading out ' + Length(buf).ToString + ' bytes. '; read := Self.Receive(@buf[0], Length(buf)); If read > 0 Then Begin debuglog := debuglog + read.ToString + ' bytes read. '; // Something was read from the buffer. Append it to the socket's data Move(buf[0], _data[_pos], read); // Increase data position Inc(_pos, read); // Reduce received length len := len - read; End Else debuglog := debuglog + 'Nothing was read. '; End; If _pos = Length(_data) Then Begin Log(LOG_TCP, debuglog.TrimRight); Log(LOG_TCP, Self.PeerAddr + ' > ' + BytesToString(_data) + ' (' + _pos.ToString + ' bytes)'); // Buffer is full. Process the data. // Decrypt the buffer... // ... Try ProcessLine(Self.PeerAddr, timestamp, TEncoding.UTF8.GetString(_data)); Except On E:Exception Do Begin Log(LOG_STD, TranslateException(E, 'processing client data')); BanList.Failed(Self.PeerAddr, 'data processing error: ' + E.Message); Self.Close; End; End; _pos := 0; SetLength(_data, 0); End Else If Not debuglog.IsEmpty Then Log(LOG_TCP, debuglog.TrimRight); Until len = 0; If (Length(_data) > 0) Or (_pos > 0) Then Log(LOG_TCP, 'Storing a fragment for ' + Self.PeerAddr + ': Data size: ' + Length(_data).ToString + ', position: ' + _pos.ToString); End; End; I know that there are a couple of premature exits before the actual data processing, but even when I added temporary logging before these, none of them was reached. I'll investigate on how I can, and will try to add TCP_NODELAY and SO_SNDBUF, but I doubt that they will make any difference. Until then, I'm really interested what are the aspects what I did not even think of until now. I'm using ICS v8.64, application is compiled using Delphi 10.4.1 as a 32-bit executable, and is executed as a Windows service on a Server 2012 R2 machine. Any help is greatly appreciated 🙂
  13. So, ~ two weeks have passed, no issues experienced so far. Packets are leaving the client and arriving to the server and even decoded properly; which means that the new read-out logic is functioning as planned. As the longest I've seen was about a week with the old solution I'm getting more confident that this was indeed the source of the issue (just to be sure I'll wait some more before considering it fixed, though). Despite the fact that that the old, TClientSocket / TServerSocket doesn't seem to be the root cause I'm glad I went on and changed them with ICS. Now I have one less concern and a ton of opportunities (like IPV6, which did not even exist when the old components were created by... Borland?) When I detect an anomaly (like an undecryptable / corrupted packet) I'm force-closing the connection with the client from the server side (by calling Self.Close, calling from a descendant of TWSocketClient). I'm assuming this also clears and destroys the queue so I don't need to read it empty first...? As my assumption was wrong with my initial receive logic, It's safer to ask I suppose 🙂
  14. aehimself

    Drone control from mobile

    This is something we (almost) all can relate to. Which makes me wonder... how many of us wrote our own password managers, instead of using an existing one? How many "fun projects" we have ready which are never used/published because we used it only to gain experience? ...or is it just me?
  15. I did this once, when I made a heavily customized DBGrid component. The custom grid had a default popup menu with basic things (copy, copy all, copy with header, etc). So if there was a popup menu assigned design time, I appended my items. If none, I created one. Alas, you don't want these options to be available when the grid is empty. So I made a custom onPopup handler to disable these items based on the selected record / field. But before assigning the new handler, I saved the old handler in a variable and executed it after I enabled / disabled my menu items. I mean, there are things what you only can solve like this, but there is a bad way of doing everything. However, I too prefer overriding the "DoCall" methods, wherever I can.
  16. aehimself

    Workaround for binary data in strings ...

    Exactly my thoughts. When I was hit with the first encoding issue I switched to TBytes. Easy to handle, but really powerful; especially with the built-in TEncoding classes. RawByteString, as it was mentioned earlier; but that also requires you to change the library itself
  17. aehimself

    Any Known Issues with ZCompressStream?

    Well, it's free and not necessarily change in the algorithm (only if you force the compression to the added LZMA). There is no justification or rationale, just experience: as I mentioned I'm using this for long years now without any major issues. This can be a coincidence, I agree; this is why I did not say it's error free and guaranteed to bring world peace. Maybe it will work for OP too, maybe not. The decision is his to make to try it, I just offered a potentional candidate I happen to know about. Change the libraries until I run out? I fixed a memory leak, added an unaccepted pull request and learned to live with it's limitations - there was no need to look for an other one. But let's not hijack the topic.
  18. aehimself

    Any Known Issues with ZCompressStream?

    Ummm... I did not? I just offered a free to use alternative.
  19. aehimself

    Any Known Issues with ZCompressStream?

    If you don't need anything fancy, you can use System.ZIP (or the updated System.ZIP2, which is a drop-in replacement offering some extras). I'm using Zip2 with smaller modifications, works like a charm.
  20. Hello, I would like to have a TMemo in my application, where the user could manually toggle the WordWrap property. When changing this property though runtime, the change has no effect on the display whatsoever. I did my homework, and I see only one reference to FWordWrap in the TCustomMemo implementation, which is this block: procedure TCustomMemo.CreateParams(var Params: TCreateParams); const ScrollBar: array[System.UITypes.TScrollStyle] of DWORD = (0, WS_HSCROLL, WS_VSCROLL, WS_HSCROLL or WS_VSCROLL); WordWraps: array[Boolean] of DWORD = (0, ES_AUTOHSCROLL); begin inherited CreateParams(Params); with Params do Style := Style and not WordWraps[FWordWrap] or ES_MULTILINE or ScrollBar[FScrollBars]; end; The setter looks like this: procedure TCustomMemo.SetWordWrap(Value: Boolean); begin if Value <> FWordWrap then begin FWordWrap := Value; RecreateWnd; end; end; Placing a breakpoint in CreateParams confirms that RecreateWnd triggers the call of CreateParams, where FWordWrap already has the most recently set value, however it does not change the behavior. Google only lead me to one question on the FPC forums, but with no viable solution. Is there a way to make this work without using a 3rd party component?
  21. aehimself

    Changing the WordWrap property of a TMemo runtime

    I can see the logic in there but this is why scrollbars can be disabled. I expected that the horizontal scrollbar will become disabled and word wrapping starts to happen. Well, anyway. At least it works now.
  22. aehimself

    Changing the WordWrap property of a TMemo runtime

    No, RichEdit works just fine. And I accidentally found the solution... it's the scrollbar. Begin TextEditor.WordWrap := Not TextEditor.WordWrap; End; doesn't do anything. Begin TextEditor.WordWrap := Not TextEditor.WordWrap; If TextEditor.WordWrap Then TextEditor.ScrollBars := ssVertical Else TextEditor.ScrollBars := ssBoth; End; works like a charm.
  23. aehimself

    Changing the WordWrap property of a TMemo runtime

    @Remy Lebeau No, not yet, was a good idea. Unfortunately though it makes no difference: Var s: String; begin s := Memo1.Lines.Text; Memo1.Clear; Memo1.WordWrap := Not Memo1.WordWrap; Memo1.Lines.Text := s; end
  24. I know. When I used the term "packet" I referred to the packet the Client sent and which has to be re-assembled on server side from the stream of bytes 🙂
  25. I'm not exactly sure if I got what you meant. The fixed size buffer is only used in the dataavailable evnt to read x bytes out from the socket. Then, there is an inner cycle to process the data in this inner buffer. If a packet ended within it, it copies the missing bytes to a different (variable size; set by the size indicator) buffer, sends that for processing, reads the size indicator and resets it. Until there is nothing else to be processed. Since I made the latest modifications, this logic seems to function properly - however there was no fragmented packet yet (when the beginning and the end arrive in two different handlers, possibly delayed)
×