Fix IllegalArgumentException - #17
Conversation
- Fix getCharIntervalMicro() to correctly return microseconds (was returning nanoseconds due to using NS_IN_A_MS instead of MICROS_IN_A_SEC) - Refactor SerialParameters constructors to use proper delegation (no-arg → full-arg chain) - Add baud rate validation (must be >= 1) to prevent division-by-zero in timing calculations - Rename DEFAULT_RS485_TX_DURING_RX to DEFAULT_RS485_RX_DURING_TX for clarity - Fix spurious Thread.currentThread().interrupt() calls on non-InterruptedException paths - Minor log message improvements for waitForTransmission
- Replace thread priority manipulation with a hybrid wait strategy (sleep -> yield -> busy spin). - Anchor target end time dynamically using System.nanoTime() inside waitForTransmission. - Prevent thread starvation and reduce syscall overhead during sub-millisecond delays. - Improve IllegalArgumentException message in SerialParameters for invalid baud rate.
Align serial timing with MODBUS over Serial Line v1.02 by using a 750µs inter-character timeout for baud rates above 19200 and keeping 1750µs for inter-frame delay. Renames `getMaxCharDelay()` to `getMaxCharTimeout()` and updates RTU receive logic to use the correct timeout value. Also simplifies the nanos wait loop to sleep until close to target, then busy-wait for precise timing.
Reduce CPU-heavy waiting in `ModbusSerialTransport` by yielding the thread before the final busy-spin for longer transmission delays. This keeps the precise timing behavior while giving the scheduler a chance to run other work.
| * | ||
| * @return Stop-bit length in bit times. | ||
| */ | ||
| public float getStopBits() { |
There was a problem hiding this comment.
Das Naming passt nicht. Es werden nicht die StopBits zurückgegeben, sondern die Bit Times.
There was a problem hiding this comment.
Ich hätte es so gelassen. In der Modbus Serial Specification wird auch nicht zwischen Stop Bits und Stop Bit Times unterschieden.
Ich habe den Zusatz "in bit times." ergänzt, um den 1,5 wert zu erklären. Aber es ist heute sowieso eher unüblich den zu verwenden.
| Thread.sleep(0); | ||
| } | ||
| } | ||
| while (System.nanoTime() < targetEndNanos) { |
There was a problem hiding this comment.
Das verursacht 100% CPU Auslastung auf einem Kern, solange es läuft. Sollte man, wie früher, davor und danach die Thread Priorität auf LOW stellen?
There was a problem hiding this comment.
Wenn überhaupt, würde ich eher Thread.yield() verwenden. Aber damit nimmt man sich die Genauigkeit.
Auf der anderen Seite hat die aktuelle implementierung die factoren x1,3 bzw. x1,7 drin. Also ist das Timing sowieso nicht exakt.
Leider ist im Code nirgends dokumentiert, wofür diese Fudge-Faktoren gedacht sind oder wie sie ermittelt wurden.
There was a problem hiding this comment.
Aber wo ruft man Thread.yield() auf? Thread.yield() in jedem while Durchlauf auszuführen ist auch nicht gut. Macht es vllt. Sinn j2mod auf Java 21 upzudaten und die neue Thread.sleep() Implementierung mit dem nanoseconds Übergabeparameter zu nutzen?
There was a problem hiding this comment.
Thread.sleep(millis, nanos) gibt es auch in Java 8 schon. Ein sleep() schickt den Thread aber immer an den OS-Scheduler und ist deshalb oft um mehrere Millisekunden ungenau, egal was man an Nanos übergibt. Java nutzt die Nanos eher zum Runden.
Für echte nano- oder mikrosekunden-Genauigkeit kommen wir um ein busy-wait nicht herum.
Zum entlasten im busy-wait sehe ich zwei möglichkeiten:
-
Thread.yield(): Da gibt es aber keine Garantie, was das OS daraus macht. Es könnte Compute freigeben, oder sich wie ein reiner Busy-Wait verhalten. Ist wieder völlig OS abhängig. -
Thread.onSpinWait(): Dafür müssten wir auf Java 9 Upgraden. Damit kann man innerhalb des busy-wait der CPU signalisieren, dass sie compute freigeben kann.
Du kannst hier gerne was ergänzen, falls du mehr weißt, aber das ist, was ich mir bisher schon zusammen gereimt habe.
| * | ||
| * @return Total bit-times per character. | ||
| */ | ||
| public double getBitsPerCharacter() { |
There was a problem hiding this comment.
Das Naming ist nicht gut. Bei getBitsPerCharacter wird wahrscheinlich an die Data Bits gedacht. Besser wäre getBitsToTransferPerCharacter()
There was a problem hiding this comment.
Es sind ja die Bits, die ein Character für die Übertragung braucht. Da es ja um Datenübertragung geht, würde ich Bit Times und Bits in diesem Zusammenhang gleichsetzen.
There was a problem hiding this comment.
Es geht nicht um den Kommentar, sondern um den Funktionsnamen. Weil es eben um die Übertragung geht, würde ich die Funktion getBitsToTransferPerCharacter() nennen. Kommentare ließt niemand.
There was a problem hiding this comment.
Ich verstehe, das Clean-Code möglichst präzise namen bevorzugt. Ich würde aber Argumentieren, dass es im Kontext von AbstractSerialConnection keinen Unterschied zwischen getBitsToTransferPerCharacter und getBitsPerCharacter gibt und ich dann eher zum einfachern Namen greifen würde.
Gleichzeitig wäre es mir aber auch nicht besonders wichtig. @michaelgrill als unparteiischer Dritter?
Fix serial timing and refactor transmission delay handling
Changes:
nanosecond timeout value out of rangeexceptions in Thread.sleep()LONG_DELAY_FUDGE_FACTOR,SHORT_DELAY_FUDGE_FACTOR)getBitsPerCharacter()andgetStopBits()to AbstractSerialConnection