use a single connection to avoid the managing overhead

This commit is contained in:
Jakob Ketterl 2021-04-11 21:04:13 +02:00
parent cb3cb50cbd
commit 4993a56235
4 changed files with 83 additions and 96 deletions

View File

@ -22,8 +22,7 @@ class AudioChopper(threading.Thread, Output, ProfileSourceSubscriber):
if mode is None or not isinstance(mode, AudioChopperMode): if mode is None or not isinstance(mode, AudioChopperMode):
raise ValueError("Mode {} is not an audio chopper mode".format(mode_str)) raise ValueError("Mode {} is not an audio chopper mode".format(mode_str))
self.profile_source = mode.get_profile_source() self.profile_source = mode.get_profile_source()
self.writersChangedOut = None (self.outputReader, self.outputWriter) = Pipe()
self.writersChangedIn = None
super().__init__() super().__init__()
def stop_writers(self): def stop_writers(self):
@ -34,11 +33,10 @@ class AudioChopper(threading.Thread, Output, ProfileSourceSubscriber):
self.stop_writers() self.stop_writers()
sorted_profiles = sorted(self.profile_source.getProfiles(), key=lambda p: p.getInterval()) sorted_profiles = sorted(self.profile_source.getProfiles(), key=lambda p: p.getInterval())
groups = {interval: list(group) for interval, group in groupby(sorted_profiles, key=lambda p: p.getInterval())} groups = {interval: list(group) for interval, group in groupby(sorted_profiles, key=lambda p: p.getInterval())}
writers = [AudioWriter(self.dsp, interval, profiles) for interval, profiles in groups.items()] writers = [AudioWriter(self.dsp, self.outputWriter, interval, profiles) for interval, profiles in groups.items()]
for w in writers: for w in writers:
w.start() w.start()
self.writers = writers self.writers = writers
self.writersChangedOut.send(None)
def supports_type(self, t): def supports_type(self, t):
return t == "audio" return t == "audio"
@ -49,7 +47,6 @@ class AudioChopper(threading.Thread, Output, ProfileSourceSubscriber):
def run(self) -> None: def run(self) -> None:
logger.debug("Audio chopper starting up") logger.debug("Audio chopper starting up")
self.writersChangedIn, self.writersChangedOut = Pipe()
self.setup_writers() self.setup_writers()
self.profile_source.subscribe(self) self.profile_source.subscribe(self)
while self.doRun: while self.doRun:
@ -67,20 +64,25 @@ class AudioChopper(threading.Thread, Output, ProfileSourceSubscriber):
logger.debug("Audio chopper shutting down") logger.debug("Audio chopper shutting down")
self.profile_source.unsubscribe(self) self.profile_source.unsubscribe(self)
self.stop_writers() self.stop_writers()
self.writersChangedOut.close() self.outputWriter.close()
self.writersChangedIn.close() self.outputWriter = None
# drain messages left in the queue so that the queue can be successfully closed
# this is necessary since python keeps the file descriptors open otherwise
try:
while True:
self.outputReader.recv()
except EOFError:
pass
self.outputReader.close()
self.outputReader = None
def onProfilesChanged(self): def onProfilesChanged(self):
logger.debug("profile change received, resetting writers...") logger.debug("profile change received, resetting writers...")
self.setup_writers() self.setup_writers()
def read(self): def read(self):
while True: try:
try: return self.outputReader.recv()
readers = wait([w.outputReader for w in self.writers] + [self.writersChangedIn]) except (EOFError, OSError):
received = [(r, r.recv()) for r in readers] return None
data = [d for r, d in received if r is not self.writersChangedIn]
if data:
return data
except (EOFError, OSError):
return None

View File

@ -38,14 +38,14 @@ class WaveFile(object):
class AudioWriter(object): class AudioWriter(object):
def __init__(self, active_dsp, interval, profiles: List[AudioChopperProfile]): def __init__(self, active_dsp, outputWriter, interval, profiles: List[AudioChopperProfile]):
self.dsp = active_dsp self.dsp = active_dsp
self.outputWriter = outputWriter
self.interval = interval self.interval = interval
self.profiles = profiles self.profiles = profiles
self.wavefile = None self.wavefile = None
self.switchingLock = threading.Lock() self.switchingLock = threading.Lock()
self.timer = None self.timer = None
(self.outputReader, self.outputWriter) = Pipe()
def getWaveFile(self): def getWaveFile(self):
tmp_dir = CoreConfig().get_temporary_directory() tmp_dir = CoreConfig().get_temporary_directory()
@ -114,19 +114,6 @@ class AudioWriter(object):
self.wavefile.writeframes(data) self.wavefile.writeframes(data)
def stop(self): def stop(self):
self.outputWriter.close()
self.outputWriter = None
# drain messages left in the queue so that the queue can be successfully closed
# this is necessary since python keeps the file descriptors open otherwise
try:
while True:
self.outputReader.recv()
except EOFError:
pass
self.outputReader.close()
self.outputReader = None
self.cancelTimer() self.cancelTimer()
try: try:
self.wavefile.close() self.wavefile.close()

View File

@ -84,40 +84,39 @@ class Js8TurboProfile(Js8Profile):
class Js8Parser(Parser): class Js8Parser(Parser):
decoderRegex = re.compile(" ?<Decode(Started|Debug|Finished)>") decoderRegex = re.compile(" ?<Decode(Started|Debug|Finished)>")
def parse(self, messages): def parse(self, raw):
for raw in messages: try:
try: profile, freq, raw_msg = raw
profile, freq, raw_msg = raw self.setDialFrequency(freq)
self.setDialFrequency(freq) msg = raw_msg.decode().rstrip()
msg = raw_msg.decode().rstrip() if Js8Parser.decoderRegex.match(msg):
if Js8Parser.decoderRegex.match(msg): return
return if msg.startswith(" EOF on input file"):
if msg.startswith(" EOF on input file"): return
return
frame = Js8().parse_message(msg) frame = Js8().parse_message(msg)
self.handler.write_js8_message(frame, self.dial_freq) self.handler.write_js8_message(frame, self.dial_freq)
self.pushDecode() self.pushDecode()
if (isinstance(frame, Js8FrameHeartbeat) or isinstance(frame, Js8FrameCompound)) and frame.grid: if (isinstance(frame, Js8FrameHeartbeat) or isinstance(frame, Js8FrameCompound)) and frame.grid:
Map.getSharedInstance().updateLocation( Map.getSharedInstance().updateLocation(
frame.callsign, LocatorLocation(frame.grid), "JS8", self.band frame.callsign, LocatorLocation(frame.grid), "JS8", self.band
) )
ReportingEngine.getSharedInstance().spot( ReportingEngine.getSharedInstance().spot(
{ {
"callsign": frame.callsign, "callsign": frame.callsign,
"mode": "JS8", "mode": "JS8",
"locator": frame.grid, "locator": frame.grid,
"freq": self.dial_freq + frame.freq, "freq": self.dial_freq + frame.freq,
"db": frame.db, "db": frame.db,
"timestamp": frame.timestamp, "timestamp": frame.timestamp,
"msg": str(frame), "msg": str(frame),
} }
) )
except Exception: except Exception:
logger.exception("error while parsing js8 message") logger.exception("error while parsing js8 message")
def pushDecode(self): def pushDecode(self):
metrics = Metrics.getSharedInstance() metrics = Metrics.getSharedInstance()

View File

@ -246,44 +246,43 @@ class Q65Profile(WsjtProfile):
class WsjtParser(Parser): class WsjtParser(Parser):
def parse(self, messages): def parse(self, data):
for data in messages: try:
try: profile, freq, raw_msg = data
profile, freq, raw_msg = data self.setDialFrequency(freq)
self.setDialFrequency(freq) msg = raw_msg.decode().rstrip()
msg = raw_msg.decode().rstrip() # known debug messages we know to skip
# known debug messages we know to skip if msg.startswith("<DecodeFinished>"):
if msg.startswith("<DecodeFinished>"): return
return if msg.startswith(" EOF on input file"):
if msg.startswith(" EOF on input file"): return
return
mode = profile.getMode() mode = profile.getMode()
if mode in ["WSPR", "FST4W"]: if mode in ["WSPR", "FST4W"]:
messageParser = BeaconMessageParser() messageParser = BeaconMessageParser()
else: else:
messageParser = QsoMessageParser() messageParser = QsoMessageParser()
if mode == "WSPR": if mode == "WSPR":
decoder = WsprDecoder(profile, messageParser) decoder = WsprDecoder(profile, messageParser)
else: else:
decoder = Jt9Decoder(profile, messageParser) decoder = Jt9Decoder(profile, messageParser)
out = decoder.parse(msg, freq) out = decoder.parse(msg, freq)
if isinstance(profile, Q65Profile) and not out["msg"]: if isinstance(profile, Q65Profile) and not out["msg"]:
# all efforts in vain, it's just a potential signal indicator # all efforts in vain, it's just a potential signal indicator
return return
out["mode"] = mode out["mode"] = mode
out["interval"] = profile.getInterval() out["interval"] = profile.getInterval()
self.pushDecode(mode) self.pushDecode(mode)
if "callsign" in out and "locator" in out: if "callsign" in out and "locator" in out:
Map.getSharedInstance().updateLocation( Map.getSharedInstance().updateLocation(
out["callsign"], LocatorLocation(out["locator"]), mode, self.band out["callsign"], LocatorLocation(out["locator"]), mode, self.band
) )
ReportingEngine.getSharedInstance().spot(out) ReportingEngine.getSharedInstance().spot(out)
self.handler.write_wsjt_message(out) self.handler.write_wsjt_message(out)
except Exception: except Exception:
logger.exception("Exception while parsing wsjt message") logger.exception("Exception while parsing wsjt message")
def pushDecode(self, mode): def pushDecode(self, mode):
metrics = Metrics.getSharedInstance() metrics = Metrics.getSharedInstance()