1

I've got the following code:

def get_input(self):
    """
    Reads command from stdin, returns its JSON form
    """
    json_string = sys.stdin.read()
    print("json string is: "+json_string)
    json_data =json.loads(json_string)
    return json_data

def accept_commands(self):
    while True:
        json_data = self.get_input()
        command = self.command_analyzer.is_command(json_data) # Check wether the command exists. Return it if it does
        #The command exists
        if command is not None:
            #The addon is not currently active
            if analyzer.intent(json_data) not in self.active_addons:
                self.activate_addon(command,json_data)

            #The addon is active and so we need to send the data to the subprocess
            else:
                self.communicate_with_addon(command,json_data,json_string)

It reads a json string that was sent to it from another process. The json is read from stdin. For some reason I get the following output:

json string is: <Some json here>
json string is: 
Traceback (most recent call last):
  File "/Users/Matan/Documents/workspace/ProjectSH/addonmanager/addon_manager.py", line 63, in <module>
    manager.accept_commands()
  File "/Users/Matan/Documents/workspace/ProjectSH/addonmanager/addon_manager.py", line 49, in accept_commands
    json_data = self.get_input()
  File "/Users/Matan/Documents/workspace/ProjectSH/addonmanager/addon_manager.py", line 42, in get_input
    json_data =json.loads(json_string)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 338, in loads
    return _default_decoder.decode(s)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 365, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 383, in raw_decode
    raise ValueError("No JSON object could be decoded")

The json is sent from the following:

class MessageReceiver:

    def __init__(self):
        '''
        Connect to the AMQP broker and starts listening for messages.
        Creates the a Popen object to pass command info to the addon_manager script (which
        is in charge of managing scripts)
        '''
        addon_manager_path = configuration.addon_manager_path()
        addon_manager_path = os.path.join(addon_manager_path,'addon_manager.py')
        execute = "python " + addon_manager_path
        self.addon_manager = subprocess.Popen(execute, stdin=subprocess.PIPE, shell=True)


        self.component_name= configuration.get_attribute("name")

        if len(sys.argv)>1:
            host_ip = sys.argv[1]
        else:
            host_ip = 'localhost'

        #Start a connection to the AMQP server
        self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=host_ip))

        #Create a channel to the server
        self.channel = self.connection.channel()

        self.channel.queue_declare(queue="kitchen")

        #callback method to be called when data is received
        #It sends the data that is received by the client to the addon_manager
        def data_received(ch, method, properties, body):
            ##TODO: Might want to add some checks. Is body a JSON? etc.
            print("writing data to addon_manager")
            self.addon_manager.communicate(body)


        self.channel.basic_consume(data_received,queue='kitchen',no_ack=True)


        self.channel.start_consuming()

What's wrong here?

matanc1
  • 6,525
  • 6
  • 37
  • 57
  • Your output indicates you called the method twice, and the second time you got an empty response. Where you expecting a second response? – Martijn Pieters Aug 25 '14 at 10:26
  • @MartijnPieters - No. I was expecting only one response (the json string). – matanc1 Aug 25 '14 at 10:28
  • Yet you have two `json string is:` lines. Your method was called twice. – Martijn Pieters Aug 25 '14 at 10:29
  • @MartijnPieters - I know it was called twice. I was expecting it to block the 2nd time until a new json arrived. I'm surprised by the empty string. – matanc1 Aug 25 '14 at 10:37
  • But your questions states at the end: *the code that writes to this process's stdin writes to it only once*. It won't block a second time because the stream has been closed. – Martijn Pieters Aug 25 '14 at 10:44

2 Answers2

2

By default, stdin.read() blocks until the entire buffer has been read. If all you could decode was one JSON object, then that is all that was sent before stdin was closed by the other process.

If you need to stream multiple JSON blocks, you should

a) not close the stream from the writing process, and b) not do a blocking read in Python.

Read in chunks or lines instead; see Loading and parsing a JSON file with multiple JSON objects in Python and How do I use the 'json' module to read in one JSON object at a time? for techniques for either reading JSON objects that are line-delimited or where multiple lines are used.

You can adapt either to produce a generator function; you'd loop over that to yield one JSON object at a time, blocking in between to wait for the next JSON object:

def get_input(self):
    for line in sys.stdin:
        yield json.loads(line)

and

def accept_commands(self):
    for json_data in self.get_input():
        # do something with `json_data`

You are using Popen.communicate() to write to the pipe. This closes the pipe after writing to it, after which it waits for the process to terminate.

If you expected the pipe to remain open, don't use Popen.communicate and instead write directly to the Popen.stdin pipe.

Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Couldn't I read until stdin is empty? Why put the timeout? Either way: The json itself in the first read attempt is okay (i.e the `json.loads()` method returns a dict). I'm wondering where the extra space came from and how to deal with it – matanc1 Aug 25 '14 at 10:23
  • @Shookie: `stdin` could be empty while you wait for more data. – Martijn Pieters Aug 25 '14 at 10:25
  • I know. As far as I knock `stdin.read` should block if it doesn't have any more data. I was hoping for it to block and continue only when another json was written to its `stdin` – matanc1 Aug 25 '14 at 10:30
  • Is there an equivalent one that blocks? – matanc1 Aug 25 '14 at 10:35
  • @Shookie: Actually, `sys.stdin` **does** block, by default. How is your stdin connected? (See [Does python's sys.stdin.read() block?](http://stackoverflow.com/q/4356989)) – Martijn Pieters Aug 25 '14 at 10:38
  • It's connected to its parent process via a pipe. It was spawned by ` self.addon_manager = subprocess.Popen(execute, stdin=subprocess.PIPE, shell=True)` and data is written into the pipe using `self.addon_manager.communicate(body)` – matanc1 Aug 25 '14 at 10:41
  • @Shookie: I am confused now; your question edit seems to imply you are expecting just one JSON object. Yet you are looping with `while True` so you are going to end up calling `self.get_input()` again and again. – Martijn Pieters Aug 25 '14 at 10:42
  • Sorry for the confusion. I'm expecting many jsons but they're sent at different times. I'm trying to read a json whenever one is sent and handle it but I get the problem stated above. – matanc1 Aug 25 '14 at 10:46
  • @Shookie: when a blanket `sys.stdin.read()` returns you get all data but the stream has been *closed*. You won't get any more data on that stream. – Martijn Pieters Aug 25 '14 at 10:47
  • I'm not entirely sure what you mean by that. As far as I know this is just a pipe that I'm reading and as long as I don't close it on either side I should be able to read from it when new data is put into it. – matanc1 Aug 25 '14 at 10:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59929/discussion-between-shookie-and-martijn-pieters). – matanc1 Aug 25 '14 at 10:54
0

It seems like you are calling get_input twice and the second there is nothing in the buffer. You should check if there is something before you try to parse JSON:

def get_input(self):
    """
    Reads command from stdin, returns its JSON form
    """
    json_string = sys.stdin.read()
    if json_string:
        print("json string is: "+json_string)
        json_data =json.loads(json_string)
        return json_data
    else:
        return None
Uxío
  • 1,333
  • 11
  • 12