Repository: kivy/kivy

Issue:
self.stop() fails with AttributeError: 'NoneType' object has no attribute 'children' in app's build()
I was trying to trap for not being able to connect to the Serial port so I could log a message, send alert, etc. and then cleanly shut down Kivy.  

When I force the error in opening the port, by adding an 's' to the device name, i.e. the real name of the serial device on my Mac is '/dev/cu.usbmodem14671', I get an error message instead of the app.stop() shutting down the app, as I had expected it to.

rafalo133 on IRC #Kivy asked me to open this.

```
self.serial = SerialPort(McuProtocol(), '/dev/cu.usbmodem14671s', reactor, ...
```

Offending code follows:

```
def build(self):

    # Build the screen manager
    self.root.ids.screen_manager.current = 'splash_screen'
    # ...

    # Open the Serial Port
    SERIAL_PORT = '/dev/ttyACM0'

    try:
        # load twisted protocol
        self.serial = SerialPort(McuProtocol(), SERIAL_PORT, reactor, baudrate=115200)
        Logger.exception('EPIC.AR: Serial: Unable to open the serial port.  Check connections!')
        self.stop()
    except SerialException as e:
        try:
            self.serial = SerialPort(McuProtocol(), '/dev/cu.usbmodem14671s', reactor, baudrate=115200)
        except SerialException as e:
            Logger.exception('EPIC.AR: Serial: Unable to open the serial port.  Check connections!')
            self.stop()

    return self.root
```

```

..
[ERROR             ] [MY.APP     ] Serial: Unable to open the serial port.  Check connections!
Traceback (most recent call last):
  File "/Users/cfo64nc/Documents/GitHub/MyApp-PROJECT/MyApp-Kivy-Python/MY.APP/main.py", line 289, in build
    self.serial = SerialPort(McuProtocol(), '/dev/cu.usbmodem14671s', reactor, baudrate=115200)
  File "/Users/cfo64nc/.env/myapp_ar_django/lib/python2.7/site-packages/twisted/internet/_posixserialport.py", line 37, in __init__
    xonxoff=xonxoff, rtscts=rtscts)
  File "/Users/cfo64nc/.env/myapp_ar_django/lib/python2.7/site-packages/serial/serialutil.py", line 182, in __init__
    self.open()
  File "/Users/cfo64nc/.env/myapp_ar_django/lib/python2.7/site-packages/serial/serialposix.py", line 247, in open
    raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
SerialException: [Errno 2] could not open port /dev/cu.usbmodem14671s: [Errno 2] No such file or directory: '/dev/cu.usbmodem14671s'
 Traceback (most recent call last):
   File "/Users/cfo64nc/Documents/GitHub/MyApp-PROJECT/MyApp-Kivy-Python/MY.APP/main.py", line 432, in <module>
     EpicARApp().run()
   File "/Users/cfo64nc/.env/myapp_ar_django/lib/python2.7/site-packages/kivy/app.py", line 802, in run
     root = self.build()
   File "/Users/cfo64nc/Documents/GitHub/MyApp-PROJECT/MyApp-Kivy-Python/MY.APP/main.py", line 292, in build
     self.stop()
   File "/Users/cfo64nc/.env/myapp_ar_django/lib/python2.7/site-packages/kivy/app.py", line 841, in stop
     for child in self._app_window.children:
 AttributeError: 'NoneType' object has no attribute 'children'
..
```



Discussion:
I added two fixes, one for lack of window (NoneType error) and the second one to force app stop when eventloop is not ready. Works like a charm.

@rafalo1333 @ilovetogetspamed confirmed this is fixed now then?
Not exactly, it gets past the 'AttributeError: 'NoneType' object has no attribute 'children'' but still doesn't stop the application at self.stop().

maybe I don't understand what stop() is supposed to do?

``` *** KIVY LOG ***
$ python main.py 
[INFO   ] [Logger      ] Record log in /home/kivyuser/.kivy/logs/kivy_17-01-04_7.txt
[INFO   ] [Kivy        ] v1.9.2-dev0
[INFO   ] [Python      ] v2.7.9 (default, Sep 17 2016, 20:26:04) 
[GCC 4.9.2]
[INFO   ] [Factory     ] 193 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_gif (img_pil, img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: sdl2
[INFO   ] [Window      ] Provider: egl_rpi
[INFO   ] [GL          ] Using the "OpenGL ES 2" graphics system
[INFO   ] [GL          ] Backend used <gl>
[INFO   ] [GL          ] OpenGL version <OpenGL ES 2.0>
[INFO   ] [GL          ] OpenGL vendor <Broadcom>
[INFO   ] [GL          ] OpenGL renderer <VideoCore IV HW>
[INFO   ] [GL          ] OpenGL parsed version: 2, 0
[INFO   ] [GL          ] Shading version <OpenGL ES GLSL ES 1.00>
[INFO   ] [GL          ] Texture max size <2048>
[INFO   ] [GL          ] Texture max units <8>
[INFO   ] [Shader      ] fragment shader: <Compiled>
[INFO   ] [Shader      ] vertex shader: <Compiled>
[INFO   ] [Window      ] virtual keyboard allowed, single mode, docked
[INFO   ] [RFID        ] Opening phidget object....
[INFO   ] [RFID        ] Waiting for attach....
[INFO   ] [RFID        ] 443695 Attached!
[INFO   ] [RFID        ] 443695 Output 0 State: False
[INFO   ] [RFID        ] 443695 Output 1 State: False
[INFO   ] [RFID        ] |------------|----------------------------------|--------------|------------|
[INFO   ] [RFID        ] |- Attached -|-              Type              -|- Serial No. -|-  Version -|
[INFO   ] [RFID        ] |------------|----------------------------------|--------------|------------|
[INFO   ] [RFID        ] |-     True -|-        Phidget RFID Read-Write -|-     443695 -|-      101 -|
[INFO   ] [RFID        ] |------------|----------------------------------|--------------|------------|
[INFO   ] [RFID        ] Number of outputs: 2 -- Antenna Status: True -- Onboard LED Status: False
[INFO   ] [RFID        ] Turning on the RFID antenna....
[INFO   ] [User Manager] Last Tag Gained: {}
[INFO   ] [EPIC.AR Main] Build Config
[ERROR  ] [EPIC.AR Main] Serial: Unable to open the serial port.  Check connections!
Traceback (most recent call last):
  File "main.py", line 174, in build
    self.ser = SerialPort(McuProtocol(), SERIAL_PORT, reactor, baudrate=115200)
  File "/usr/local/lib/python2.7/dist-packages/twisted/internet/_posixserialport.py", line 37, in __init__
    xonxoff=xonxoff, rtscts=rtscts)
  File "/usr/local/lib/python2.7/dist-packages/serial/serialutil.py", line 236, in __init__
    self.open()
  File "/usr/local/lib/python2.7/dist-packages/serial/serialposix.py", line 268, in open
    raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
SerialException: [Errno 2] could not open port /dev/ttyACM0: [Errno 2] No such file or directory: '/dev/ttyACM0'
[INFO   ] [OSC         ] using <multiprocessing> for socket
[INFO   ] [ProbeSysfs  ] device match: /dev/input/event2
[INFO   ] [ProbeSysfs  ] unable to found provider 
[INFO   ] [ProbeSysfs  ] fallback on hidinput
[INFO   ] [HIDInput    ] Read event from </dev/input/event2>
[INFO   ] [ProbeSysfs  ] device match: /dev/input/event2
[INFO   ] [HIDInput    ] Read event from </dev/input/event2>
[INFO   ] [Base        ] Start application main loop
[INFO   ] [HIDMotionEvent] using <FT5406 memory based driver>
[INFO   ] [HIDMotionEvent] <FT5406 memory based driver> range ABS X position is 0 - 800
[INFO   ] [HIDMotionEvent] <FT5406 memory based driver> range ABS Y position is 0 - 480
[INFO   ] [HIDMotionEvent] <FT5406 memory based driver> range position X is 0 - 800
[INFO   ] [HIDMotionEvent] <FT5406 memory based driver> range position Y is 0 - 480
[INFO   ] [HIDMotionEvent] using <FT5406 memory based driver>
[INFO   ] [Support     ] Starting twisted reactor
[INFO   ] [HIDMotionEvent] <FT5406 memory based driver> range ABS X position is 0 - 800
[INFO   ] [HIDMotionEvent] <FT5406 memory based driver> range ABS Y position is 0 - 480
[INFO   ] [HIDMotionEvent] <FT5406 memory based driver> range position X is 0 - 800
[INFO   ] [GL          ] NPOT texture support is available
[INFO   ] [HIDMotionEvent] <FT5406 memory based driver> range position Y is 0 - 480
```

``` *** Python main.py snippet***
...
        # Open the Serial Port
        try:
            self.ser = SerialPort(McuProtocol(), SERIAL_PORT, reactor, baudrate=115200)
        except SerialException as e:
            Logger.exception('Main: Serial: Unable to open the serial port.  Check connections!')

            # get rid of the navbar
            self.root.remove_widget(self.root.ids.nav_bar)

            ss_text = 'Critical Error: Could not open port!\n\nPlease Contact EPIC.AR Service'
            ss_screen = self.root.ids.screen_manager.get_screen('splash_screen')
            ss_screen.ids['ss_label'].font_size = 48
            ss_screen.ids['ss_label'].color = h2c('#FF0000')
            ss_screen.ids['ss_label'].text = ss_text

            self.stop()
...
```

Confirm you are saying that the kivy window does not close? Or does the Window close but the application continues to run on the command line?
The window does not close.

I’ll try it with a virgin program.

> On Jan 6, 2017, at 12:10 AM, FeralBytes <notifications@github.com> wrote:
> 
> Confirm you are saying that the kivy window does not close? Or does the Window close but the application continues to run on the command line?
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub <https://github.com/kivy/kivy/issues/4634#issuecomment-270832731>, or mute the thread <https://github.com/notifications/unsubscribe-auth/ADHZnIeqFOasLpJfdfu5nPo-Bdspx-upks5rPczTgaJpZM4KGhpR>.
> 


Please post a runnable example so we can readily reproduce the bug.
Posted at https://gist.github.com/ilovetogetspamed/49ff6d590b294c2d08da4965150452a8.  I hope that helps

> On Mar 17, 2017, at 6:08 AM, dessant <notifications@github.com> wrote:
> 
> Please post a runnable example so we can readily reproduce the bug.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub <https://github.com/kivy/kivy/issues/4634#issuecomment-287315422>, or mute the thread <https://github.com/notifications/unsubscribe-auth/ADHZnNkSzhFJX3JEOb0WuzJFH_EQF3Dpks5rmlupgaJpZM4KGhpR>.
> 


Its a known bug, I've tried to fix it at #4637, but it needed some additional checks.
@ilovetogetspamed, thanks, though i was thinking about a simple self-contained example, i'm not sure if twisted and the rest is even needed to reproduce it.
Rafal,

I suppose instead of trying to stop the application in the build. I could schedule the error message/shutdown after a period of time at which time the application is more apt to not miss the stop message.

Thanks for working on it though.  I’m great at finding unexpected results in code ;-)

Chris

> On Mar 17, 2017, at 9:22 AM, Rafał Kaczor <notifications@github.com> wrote:
> 
> Its a known bug, I've tried to fix it at #4637 <https://github.com/kivy/kivy/pull/4637>, but it needed some additional checks.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub <https://github.com/kivy/kivy/issues/4634#issuecomment-287352064>, or mute the thread <https://github.com/notifications/unsubscribe-auth/ADHZnNifEqzihSqjedglOtV5ZorIqps8ks5rmokYgaJpZM4KGhpR>.
> 


After seeing #4637, i think this will suffice for testing the remaining issue:

```python
from kivy.app import App
from kivy.uix.label import Label


class TestApp(App):

    def on_start(self, *args):
        self.stop()

    def build(self):
        # self.stop()
        return Label(text='test')


if __name__ == '__main__':
    TestApp().run()
```
I think I have 2 ways of fixing my old PR, and I dont know which is better, or maybe in another words, safer.

1) We can add `Eventloop.status = 'stopping'` while scheduling, and stop future schedule tries if we see that status; although I dont know how it affects the Eventloop API as it needs to process some events basing on the current status

2) we can use `_eventloop_stopping = True` global, which would not affect Eventloop API at all

2 is definitely safer, but 1 seems "cleaner" programatically.

What do you think @dessant  ?
@rafalo1333, dunno, I see @matham reviewed your previous pr, you may want to take it up with him.
`Eventloop.stopping` is probably best. Just need to remember to clear it always after we are stopped.

Resolving patch:
diff --git a/kivy/base.py b/kivy/base.py
--- a/kivy/base.py
+++ b/kivy/base.py
@@ -106,6 +106,7 @@ def __init__(self):
         self.input_events = []
         self.postproc_modules = []
         self.status = 'idle'
+        self.stopping = False
         self.input_providers = []
         self.input_providers_autoremove = []
         self.event_listeners = []
@@ -190,6 +191,7 @@ def stop(self):
         # ensure any restart will not break anything later.
         self.input_events = []
 
+        self.stopping = False
         self.status = 'stopped'
         self.dispatch('on_stop')
 
@@ -510,7 +512,12 @@ def stopTouchApp():
     '''Stop the current application by leaving the main loop'''
     if EventLoop is None:
         return
+    if EventLoop.status in ('stopped', 'closed'):
+        return
     if EventLoop.status != 'started':
+        if not EventLoop.stopping:
+            EventLoop.stopping = True
+            Clock.schedule_once(lambda dt: stopTouchApp(), 0)
         return
     Logger.info('Base: Leaving application in progress...')
     EventLoop.close()


Test patch: