Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First sample is lost #89

Open
dcnieho opened this issue Nov 20, 2024 · 3 comments
Open

First sample is lost #89

dcnieho opened this issue Nov 20, 2024 · 3 comments

Comments

@dcnieho
Copy link

dcnieho commented Nov 20, 2024

This is similar to #95.
I have simplified the working example code from there to the code posted at the end, which still works fine.
However, an almost the same example always loses the first sample on the return leg. this is the code

send.py:

import random
import time
from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_byprop

def main():
    # out stream
    info = StreamInfo('Wally_finder', 'Wally_master', 1, 0, "string", "myuidw43536")
    outlet = StreamOutlet(info)

    # return/answer stream
    streams = resolve_byprop("type", "Wally_client")
    inlet = StreamInlet(streams[0])

    markernames = ["Test", "Blah", "Marker", "XXX", "Testtest", "Test-1-2-3"]
    while True:
        # send a random sample
        msg = random.choice(markernames)
        outlet.push_sample([msg])

        # wait for reply
        t0 = time.monotonic()
        sample, timestamp = inlet.pull_sample(timeout=1)
        if sample:
            print("sent %s, got %s at time %s" % (msg, sample[0], timestamp))
        else:
            print('sent %s, got nothing' % (msg))
        
        # throttle
        time.sleep(max(0.,t0+1-time.monotonic()))

if __name__ == "__main__":
    main()

receive.py:

from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_byprop

def send(msg, outlet):
    print(f'sending: {msg}')
    outlet.push_sample([msg])

def main():
    # first resolve stream from which we receive requests
    print("looking for a marker stream...")
    streams = resolve_byprop("type", "Wally_master")
    inlet = StreamInlet(streams[0])

    # open answer stream
    info = StreamInfo('Wally_finder', 'Wally_client', 1, 0, "string", "fdg")
    outlet = StreamOutlet(info)
    for _ in range(5):
        send('warm up', outlet)

    while True:
        # wait to receive a request
        sample, timestamp = inlet.pull_sample()

        if sample:
            # reply with the same string
            send(sample[0], outlet)


if __name__ == "__main__":
    main()

Here some example output i get from send.py. The first reply sample, as shown in this example, is always missing, regardless of whether i start send.py or receive.py first:

python send.py
2024-11-20 16:13:36.969 (   0.001s) [        5979C50B]         api_config.cpp:231   INFO| Loaded default config
2024-11-20 16:13:36.970 (   0.001s) [        5979C50B]             common.cpp:64    INFO| git:a0fc2fde92a10f4cb5fccbc5552228b865f17379/branch:refs/tags/v1.15.0/build:Release/compiler:MSVC-19.0.24245.0/link:SHARED
sent Test, got nothing
sent XXX, got XXX at time 9165.2725887
sent Blah, got Blah at time 9166.2739955

receive.py output in the mean time:

looking for a marker stream...
2024-11-20 16:13:38.307 (   0.001s) [        3215F40A]         api_config.cpp:231   INFO| Loaded default config
2024-11-20 16:13:38.811 (   0.505s) [        3215F40A]             common.cpp:64    INFO| git:a0fc2fde92a10f4cb5fccbc5552228b865f17379/branch:refs/tags/v1.15.0/build:Release/compiler:MSVC-19.0.24245.0/link:SHARED
sending: warm up
sending: warm up
sending: warm up
sending: warm up
sending: warm up
sending: Test
sending: XXX
sending: Blah 

Note that sending one or many earlier warm up packets also doesn't matter, they are also lost. I am not sure how this is different from the example code from #85, nor why it is possible to lose samples at all. That shouldn't be possible, right?

example code from #85 (works):
ping.py

import time
from pylsl import StreamInlet, StreamOutlet, StreamInfo, resolve_byprop

info = StreamInfo('PingStream', 'Wally_master', 1, 0, 'string', 'ping_stream')
outlet = StreamOutlet(info)
print("Ping stream created.")

print("Looking for a Pong stream...")
streams = None
while streams is None:
    streams = resolve_byprop('type', 'Wally_client')
    if not streams:
        print("No Pong stream found, retrying...")
        time.sleep(1)

inlet = StreamInlet(streams[0])
print("Pong stream found.")


# Interaction loop
i = 'a'

outgoing_sample = [i]
outlet.push_sample(outgoing_sample)
print(f"Sent to Pong: {outgoing_sample}")
while True:
    incoming_sample, timestamp = inlet.pull_sample(timeout=0.0)

    if incoming_sample:
        print(f"Received from Pong: {incoming_sample} at {timestamp}")

        i = chr(ord(i)+1)
        outgoing_sample = [i]
        outlet.push_sample(outgoing_sample)
        print(f"Sent to Pong: {outgoing_sample}")

    # Sleep to simulate time between responses
    time.sleep(1)

pong.py:

import time
from pylsl import StreamInlet, StreamOutlet, StreamInfo, resolve_byprop

info = StreamInfo('PongStream', 'Wally_client', 1, 0, 'string', 'pong_stream')
outlet = StreamOutlet(info)
print("Pong stream created.")

print("Looking for a Ping stream...")
streams = None
while streams is None:
    streams = resolve_byprop('type', 'Wally_master')
    if not streams:
        print("No Ping stream found, retrying...")
        time.sleep(1)

inlet = StreamInlet(streams[0])
print("Ping stream found.")


# Interaction loop
while True:
    # Receive a sample from the Ping stream
    incoming_sample, timestamp = inlet.pull_sample(timeout=0.0)

    if incoming_sample:
        print(f"Received from Ping: {incoming_sample} at {timestamp}")

        outgoing_sample = incoming_sample
        
        outlet.push_sample(outgoing_sample)
        print(f"Sent to Ping: {outgoing_sample}")

    # Sleep to simulate time between responses
    time.sleep(1)
@dcnieho
Copy link
Author

dcnieho commented Nov 21, 2024

That was not a great bug report. I have narrowed it down a bit, but understand nothing. See the following scripts (order of starting doesn't matter).

send.py

import random
import time

from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_byprop

# out stream
info = StreamInfo('Wally_finder', 'Wally_master', 1, 0, "string", "myuidw43536")
outlet = StreamOutlet(info)

# return/answer stream
streams = resolve_byprop("type", "Wally_client")
inlet = StreamInlet(streams[0])

markernames = ["Test", "Blah", "Marker", "XXX", "Testtest", "Test-1-2-3"]
while True:
    # send a random sample
    msg = random.choice(markernames)
    outlet.push_sample([msg])

    # wait for reply
    t0 = time.monotonic()
    sample, timestamp = inlet.pull_sample()
    if sample:
        print("sent %s, got %s at time %s" % (msg, sample[0], timestamp))
    else:
        print('sent %s, got nothing' % (msg))
    
    # throttle
    time.sleep(max(0.,t0+1-time.monotonic()))

receive.py

from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_byprop
import time

def send(msg, outlet):
    print(f'sending: {msg}')
    outlet.push_sample([msg])

# first resolve stream from which we receive requests
print("looking for a marker stream...")
streams = resolve_byprop("type", "Wally_master")
inlet = StreamInlet(streams[0])

# open answer stream
info = StreamInfo('Wally_finder', 'Wally_client', 1, 0, "string", "fdg")
outlet = StreamOutlet(info)
time.sleep(1)

for _ in range(1):
    send('warm up', outlet)

while True:
    # wait to receive a request
    sample, timestamp = inlet.pull_sample(timeout=0.0)

    if sample:
        # reply with the same string
        send(sample[0], outlet)
    #time.sleep(1)

Note that there are two sleeps in receive.py, one after opening the outlet, and one in the loop. With the one after opening the outlet active, and the loop commented out, i get the following output from send.py:

sent XXX, got warm up at time 21275.2454636
sent Blah, got Blah at time 21276.2302867
sent Testtest, got Testtest at time 21277.2308909
sent Marker, got Marker at time 21278.2323343
sent Marker, got Marker at time 21279.2332656

and receive.py:

sending: warm up
sending: Blah
sending: Testtest, got Testtest at time 21277.2308909
sending: Marker
sending: Marker

As you see, receive never got the first sample from send, but the warm up message sent earlier is picked up by send.

With the sleep in the loop active and the one after opening the outlet commented out, the output is:
send.py:

sent Test, got Test at time 21572.5591904
sent XXX, got XXX at time 21573.5600448
sent Marker, got Marker at time 21574.5607532
sent Blah, got Blah at time 21575.5608379
sent XXX, got XXX at time 21576.5610598

receive.py:

sending: warm up
sending: Test
sending: XXX
sending: Marker
sending: Blah
sending: XXX

So, all main messages are ping ponged, but the warm up message is lost. This could work as a workaround, were it not for the below:

If i now shorten the sleep in the loop in receive.py to something shorter than .1 s (haven't really narrowed it down), like 0.01 s, and add a timeout to send.py's sample pull (inlet.pull_sample(timeout=1)) because otherwise no progress is made, the output is:
send.py:

sent Blah, got nothing
sent Test, got Test at time 21760.5788021
sent Testtest, got Testtest at time 21761.5882271
sent Test, got Test at time 21762.5803529
sent Marker, got Marker at time 21763.5892445

receive.py:

sending: warm up
sending: Blah
sending: Test
sending: Testtest
sending: Test
sending: Marker

So now both "warm up" and the first reply ("Blah") are lost.

What is going on here? How to get this to work reliably? Adding a time.sleep(1) after opening the inlet in send.py changes nothing.

@dcnieho
Copy link
Author

dcnieho commented Nov 21, 2024

one further note. I just saw i was using pylsl 1.15.0. Upgrading to 1.16.2 changes nothing.

@dcnieho
Copy link
Author

dcnieho commented Nov 22, 2024

Ok, now maybe we're getting to some diagnosis. Consider the following:

send.py:

import random
import time

from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_byprop

# out stream
info = StreamInfo('Wally_finder', 'Wally_master', 1, 0, "string", "myuidw43536")
outlet = StreamOutlet(info)

# return/answer stream
streams = resolve_byprop("type", "Wally_client")
inlet = StreamInlet(streams[0])

markernames = ["Test", "Blah", "Marker", "XXX", "Testtest", "Test-1-2-3"]
while True:
    # send a random sample
    msg = random.choice(markernames)
    hc = outlet.have_consumers()
    outlet.push_sample([msg])

    # wait for reply
    t0 = time.monotonic()
    sample, timestamp = inlet.pull_sample(timeout=.1)
    if sample:
        print("sent %s, got %s at time %s (have consumers: %d)" % (msg, sample[0], timestamp, hc))
    else:
        print('sent %s, got nothing (have consumers: %d)' % (msg, hc))
    
    # throttle
    time.sleep(max(0.,t0+1-time.monotonic()))

receive.py:

from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_byprop

def send(msg, outlet: StreamOutlet):
    print(f'sending: {msg}, have consumers: {outlet.have_consumers()}')
    outlet.push_sample([msg])

# first resolve stream from which we receive requests
streams = resolve_byprop("type", "Wally_master")
inlet = StreamInlet(streams[0])

# open answer stream
info = StreamInfo('Wally_finder', 'Wally_client', 1, 0, "string", "fdg")
outlet = StreamOutlet(info)

while True:
    # wait to receive a request
    sample, timestamp = inlet.pull_sample(timeout=0.1)

    if sample:
        # reply with the same string
        send(sample[0], outlet)

That is, i got rid of all the sleeps, and added a check whether the outlet has any consumer (which it should, since both outlets have a registered inlet on the other side).

output of send.py:

sent Testtest, got nothing (have consumers: 1)
sent Marker, got Marker at time 2792.6298752 (have consumers: 1)
sent Test-1-2-3, got Test-1-2-3 at time 2793.6303128 (have consumers: 1)
sent Marker, got Marker at time 2794.6305511 (have consumers: 1)

output of receive.py:

sending: Testtest, have consumers: False
sending: Marker, have consumers: True
sending: Test-1-2-3, have consumers: True
sending: Marker, have consumers: True

So, the first reply of receiver.py is not sent as there are no registered consumers for its output stream.

So i thought maybe its the ordering of the inlet and outlet creation. If i change receive.py to first open its outlet as well, like this:

from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_byprop

def send(msg, outlet: StreamOutlet):
    print(f'sending: {msg}, have consumers: {outlet.have_consumers()}')
    outlet.push_sample([msg])

# open answer stream
info = StreamInfo('Wally_finder', 'Wally_client', 1, 0, "string", "fdg")
outlet = StreamOutlet(info)

# first resolve stream from which we receive requests
streams = resolve_byprop("type", "Wally_master")
inlet = StreamInlet(streams[0])

while True:
    # wait to receive a request
    sample, timestamp = inlet.pull_sample(timeout=0.1)

    if sample:
        # reply with the same string
        send(sample[0], outlet)

we get the following output:
output of send.py:

sent Blah, got nothing (have consumers: 0)
sent Testtest, got Testtest at time 2983.1281433 (have consumers: 1)
sent Testtest, got Testtest at time 2984.1283964 (have consumers: 1)
sent Marker, got Marker at time 2985.1287455 (have consumers: 1)

output of receive.py:

sending: Testtest, have consumers: True
sending: Testtest, have consumers: True
sending: Marker, have consumers: True

So no, that just moves the problem.

Adding a 1s sleep in both scripts after creating the inlet changes nothing in the output.

Is this expected behavior? How do i get this bi-directional communication to work reliably?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant