labrad / pylabrad

python interface for labrad
51 stars 31 forks source link

Asynchronous Connection and .future Error #331

Closed mserlin closed 7 years ago

mserlin commented 7 years ago

An error is being thrown when using the @settings future implementation in combination with an asynchronous labrad connection. The code below (embedded in a GUI) throws an error when .future is used. However, this code works fine when the connection to labrad is made with cxn = labrad.connect().

cxn = yield connectAsync(name = 'Labrad Connection')
dac_adc = yield cxn.dac_adc
yield dac_adc.select_device()

#Start a buffer ramp. This immediately returns True if communication to the DAC ADC is successful
yield dac_adc.buffer_ramp([0],[0],[0],[2],100,10000)

#Uses future to appropriately wait for the buffer ramp to complete before polling the results. Error is thrown here
d_read = yield dac_adc.serial_poll.future(4, bias_points)

# Read the results of the buffer ramp
d_tmp = d_read.result()

Anyone know what the problem might be?

DanielSank commented 7 years ago

I don't see @settings anywhere in the posted code. Can you post a full example that reproduces the problem?

ejeffrey commented 7 years ago

I think the "future" method is only supported on synchronous clients. For asynchronous clients, requests always return twisted.Deferreds or similar, which you can simply yield directly. future() is only needed in a synchronous client to specificy that a specific call should be run in the background. I think if you just change this to:

d_read = yield dac_adc.serial_poll(4, bias_points)

It should work properly.

mserlin commented 7 years ago

Thank you for the response Daniel, sorry about not including all the information. The @setting is found in the device server written for the dac adc. The server communicates with the DAC/ADC through serial communication. Below the is the code for the serial_poll command.

    @setting(1919, nPorts = 'i', steps ='i', returns = '**v[]' )
    def serial_poll(self, c, nPorts, steps):
        '''
        SERIAL_POLL return the voltages read by the BUFFER_RAMP. The channels are returned in the same order that they were specified on the BUFFER_RAMP.
        '''
        dev=self.selectedDevice(c)
        voltages=[]
        channels=[]
        data = yield dev.readByte(steps*nPorts*2)
        data = list(data)

        for x in xrange(nPorts):
            channels.append([])

        for x in xrange(0, len(data), 2):
            b1=int(data[x].encode('hex'),16)
            b2=int(data[x+1].encode('hex'),16)
            decimal = twoByteToInt(b1,b2)
            voltage = map2(decimal,0,65536,-10.0,10.0)
            voltages.append(voltage)

        for x in xrange(0, steps*nPorts, nPorts):
            for y in xrange(nPorts):
                channels[y].append(voltages[x+y])

        yield dev.read()

        returnValue(channels)

ejeffrey, that does prevent this error from being thrown, but it doesn't do quite what I would like. The problem is that when the dac_adc.buffer_ramp() method is called, it immediately returns "True" when the communication is successful. It then proceeds to do an operation that requires several seconds and the outputs, being read with serial_poll, won't be available until the end of that time period. However, the moment "True" is received, if future isn't used, then serial poll would immediately be called trying to read outputs that aren't there. With a synchronous connection, future solves this problem though admittedly I'm very unclear on the specifics of what it's doing.

ejeffrey commented 7 years ago

@mserlin OK, I have no idea why that is working in the first place :) I suspect it is some weird interaction with the context lock on the server that is preventing the serial_poll from being executed until the buffer ramp is complete, but I don't quite know where that would be happening or how the extra future call here would be changing the behavior. This is not the intended use of future, which is merely meant to control waiting for communication. Can you post the code to buffer_ramp? I think that is where the problem is.

Basically I think you need to do one of two (maybe three) things.

1) Make buffer_ramp not return until the ramp is complete. Then, when you yield() the buffer_ramp result, it won't return until the data is ready. The downside of this is that synchronous clients will (by default) block for several seconds.

2) Add a helper setting like wait_for_ramp() that checks to see if the most recent ramp is complete. This would allow you to overlap other processing during the ramp, and then wait before calling serial_poll(). I would say this is the best solution

3) Have serial_poll execute the wait before attempting to read data.

mserlin commented 7 years ago

@ejeffrey Thank you, I'll find a workaround as you suggested.