0

I have an object.

This object has a connect() method which spawns a pexpect process.

The process that's spawned is a custom serial interface. On launch, this tool prints a menu of serial devices to connect to, like so:

libftdi device (0): A6005jpt
libftdi device (1): acFX9DQf
Serial device (a) : /dev/cu.Bluetooth-PDA-Sync
Select a device by its letter (^D to abort): 

My connect() determines which number to pass based on a given devicename (e.g. 'acFX9DQf'): (self.connection is the pexpect spawn)

USBSERIAL_DEVICE_NAME = "acFX9DQf"    

try:
    while self.connection.expect(['libftdi device \(([0-9])\): (.*)','Serial device']) == 0:
        if self.connection.match.group(2).strip() == USBSERIAL_DEVICE_NAME:
            # do stuff
except:
    # do stuff

Now, my problem is that I connect()/kill() the process multiple times in my main logic and sometimes, one of those times, connect() decides to throw a pexpect.TIMEOUT exception, unexpectedly.

For example, when I add the following debug statements to my logic, like so:

USBSERIAL_DEVICE_NAME = "acFX9DQf"

try:
    while self.connection.expect(['libftdi device \(([0-9])\): (.*)','Serial device'], timeout=10) == 0:
        print "MATCHED A DEVICE LINE!"
        if self.connection.match.group(2).strip() == USBSERIAL_DEVICE_NAME:
            print "MATCHED THE DESIRED USBSERIAL..."

...I get this output for many calls of connect():

libftdi device (0): A6005jpt
MATCHED A DEVICE LINE!
libftdi device (1): acFX9DQf
MATCHED A DEVICE LINE!
MATCHED THE DESIRED USBSERIAL...
Serial device (a) : /dev/cu.Bluetooth-PDA-Sync
Select a device by its letter (^D to abort): 1

...then, one of my connect() calls will unexpectedly do....

libftdi device (0): A6005jpt
MATCHED A DEVICE LINE!
libftdi device (1): acFX9DQf
Serial device (a) : /dev/cu.Bluetooth-PDA-Sync
Select a device by its letter (^D to abort): MATCHED A DEVICE LINE!

<<EXCEPTION>>

But, if I revise my code to this:

try:
    while self.connection.expect(['libftdi device \(([0-9])\): (.*)','Serial device'], timeout=10) == 0:
        devicename = self.connection.match.group(2).strip()
        if devicename == USBSERIAL_DEVICE_NAME:
            # do stuff

My problems go away! I can run it repeatedly and no problems will occur - no exceptions, no nothin'.

So wot in the heck is going on here? I'm not even sure where to begin with this problem.

outis
  • 75,655
  • 22
  • 151
  • 221
eastydude5
  • 597
  • 2
  • 5
  • 17
  • 2
    OK, so what exception are you getting and what change fixes it? – Gabe May 26 '11 at 03:11
  • When you introduce `devicename`, is the output ever the same as for when the first code fails (that is, the "MATCHED A DEVICE LINE!" comes after the prompt)? – outis May 26 '11 at 06:37
  • @Gabe - I was getting a pexpect.TIMEOUT. – eastydude5 May 26 '11 at 16:54
  • @outis - It always fails randomly. My main business logic connect()/kills() 7 times. Sometimes connect() throws the exception on time 1 or time 4. It's weird. – eastydude5 May 26 '11 at 16:54
  • that doesn't answer my question. I was asking about the behavior of code that you say you can "run [...] repeatedly and no problems will occur." Does it show the same output as when the first code fails? The reason I ask is to figure out why it isn't failing. – outis May 26 '11 at 20:06

1 Answers1

1

I'm guessing you're getting a TIMEOUT exception. Based on where the "MATCHED A DEVICE LINE!" message is in the output, my hypothesis is that the spawn instance has multiple lines when it tests for a match. When pexpect compiles the regexes, it sets re.DOTALL, so the .* includes the newline and the additional lines. This causes the test against USBSERIAL_DEVICE_NAME to fail and the loop continues its next iteration. The next call to expect blocks because there is no additional input and you get a timeout.

To fix this, you can pass in your own compiled regexes (which will lack the re.DOTALL flag) or change the .* so it explicitly doesn't match the newline (e.g. \S*).

As for why storing the matched group in a variable seems to fix things, I can only guess this introduces a subtle timing change that prevents the call to expect from receiving multiple lines of input at once.

outis
  • 75,655
  • 22
  • 151
  • 221
  • Thanks so much for the solution. My guess was it was a timing/pexpect bugginess thing. Just wanted to be sure I wasn't missing some key Python lesson here... – eastydude5 May 26 '11 at 16:54
  • Changing .* to \S* fixed it!! Perfect answer! – eastydude5 May 26 '11 at 17:07
  • @eastydude: timing, yes, bug, no. pexpect's use of `re.DOTALL` is designed. I don't know the reason, but it was probably to fix a bug. – outis May 26 '11 at 20:04