diff --git a/gpsd-busywait.patch b/gpsd-busywait.patch new file mode 100644 index 0000000..784cfd3 --- /dev/null +++ b/gpsd-busywait.patch @@ -0,0 +1,84 @@ +commit e5ba7aa2af74fd22ebbd5c4a6624edcf983863de +Author: Michal Schmidt +Date: Fri Aug 4 16:53:01 2023 +0200 + + gps/gps.py.in: no busy-waiting when reading from gpsd socket + + ubxtool keeps one CPU 100% busy while it waits for data to read from the + gpsd socket. Running it under strace showed that it calls select() with + zero timeout in a loop: + + ... + 11:02:34.049629 pselect6(4, [3], [], [], {tv_sec=0, tv_nsec=0}, NULL) = 0 (Timeout) + 11:02:34.049649 pselect6(4, [3], [], [], {tv_sec=0, tv_nsec=0}, NULL) = 0 (Timeout) + 11:02:34.049670 pselect6(4, [3], [], [], {tv_sec=0, tv_nsec=0}, NULL) = 0 (Timeout) + ... + + The busy waiting can be eliminated by passing the actual timeout value + to select(). In the reading loop in gps.py, the remaining time can be + easily calculated and passed as the argument to the self.ser.waiting() + function (which is basically a select() wrapper). + + Fixing this problem exposed a bug in how the received bytes are decoded. + decode_func may not consume all input at once. Consumable input may be + left in self.out until decode_func returns zero, indicating that it + could not process any more input. So decode_func must be called in a + loop each time a buffer is received from the socket. The busy waiting + was hiding this issue, because decode_func was being called all the + time. + + The "elif self.input_is_device:" branch probably needs similar + treatment, but I am testing only the gpsd usecase. + +diff --git a/gps/gps.py.in b/gps/gps.py.in +index 623a750a0..14d7707ab 100644 +--- a/gps/gps.py.in ++++ b/gps/gps.py.in +@@ -384,10 +384,11 @@ class gps_io(object): + if self.gpsd_host is not None: + # gpsd input + start = monotonic() +- while (monotonic() - start) < input_wait: ++ remaining_time = input_wait ++ while remaining_time > 0: + # First priority is to be sure the input buffer is read. + # This is to prevent input buffer overuns +- if 0 < self.ser.waiting(): ++ if 0 < self.ser.waiting(remaining_time): + # We have serial input waiting, get it + # No timeout possible + # RTCM3 JSON can be over 4.4k long, so go big +@@ -397,17 +398,22 @@ class gps_io(object): + raw_fd.write(polybytes(new_out)) + self.out += new_out + +- consumed = decode_func(self.out) +- # TODO: the decoder shall return a some current +- # statement_identifier # to fill last_statement_identifier +- last_statement_identifier = None +- # +- self.out = self.out[consumed:] +- if ((expect_statement_identifier and +- (expect_statement_identifier == +- last_statement_identifier))): +- # Got what we were waiting for. Done? +- ret_code = 0 ++ while True: ++ consumed = decode_func(self.out) ++ if consumed == 0: ++ break ++ # TODO: the decoder shall return a some current ++ # statement_identifier # to fill last_statement_identifier ++ last_statement_identifier = None ++ # ++ self.out = self.out[consumed:] ++ if ((expect_statement_identifier and ++ (expect_statement_identifier == ++ last_statement_identifier))): ++ # Got what we were waiting for. Done? ++ ret_code = 0 ++ ++ remaining_time = start + input_wait - monotonic() + + elif self.input_is_device: + # input is a serial device diff --git a/gpsd-minimal.spec b/gpsd-minimal.spec index 3230ba2..2bf0350 100644 --- a/gpsd-minimal.spec +++ b/gpsd-minimal.spec @@ -20,6 +20,8 @@ Source11: gpsd.sysconfig Patch1: gpsd-ipv6.patch # fix some issues reported by coverity and shellcheck Patch2: gpsd-scanfixes.patch +# fix busy wait when reading from gpsd socket +Patch3: gpsd-busywait.patch BuildRequires: gcc BuildRequires: dbus-devel @@ -61,6 +63,7 @@ This package contains various clients using gpsd. %setup -q -n gpsd-%{version} -a 1 %patch -P 1 -p1 -b .ipv6 %patch -P 2 -p1 -b .scanfixes +%patch -P 3 -p1 -b .busywait # add note to man pages about limited support sed -i ':a;$!{N;ba};s|\(\.SH "[^"]*"\)|.SH "NOTE"\n%{note1}\n%{note2}\n\1|3' \