Roku Developer Program

Developers and content creators—a complete solution for growing an audience directly.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
JesUltra
Level 7

Channel crash upon assigning to Poster.height

My channel is using a RowList with itemComponentName="CItem" as given below, (based on CItem.xml from some Roku sample code.)

I am seeing on my channel dashboard, that rarely, but consistently, my channel crashes on line 70 of this xml file (about once for every 100 hours played).  Unfortunately, the Roku dashboard does not provide any error message, only the line number.  I have not been able to reproduce the issue on my own development setup.

The line that crashes is always m.Poster.height = height , and it is odd because the immediately preceding line m.Poster.width = width never crashes, and just preceding the two is a check that both variables height and width are strictly positive.

This issue has been nagging me for more than a year, and I have made the code increasingly defensive, to the point that you see now.  Does anyone have an idea what could be the issue?

<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2016 Roku Corp.  All Rights Reserved. -->

<component name="CItem" extends="Group">

  <children>
      <Poster id="poster" loadDisplayMode="scaleToZoom"/>
      <Rectangle id="overlay"
		 color="#FFFFFF"
		 opacity="0.6"
	      visible="false"/>
      <Poster id="overlayicon" loadDisplayMode="scaleToZoom"
	      uri="pkg:/images/dk-flag201x150.png"
	      width="201"
	      height="150"
	      visible="false"/>
      <Label  id="label"    font="fontSmiley FrustratedmallSystemFont" text=""
	      color="#383838"
	      maxLines="3"
	      wrap="true" horizAlign="left" />
      <Rectangle  id="progress"
        color="#FF001E"
        opacity="1"/>
      <Rectangle  id="progressbg"
        color="#C9C9C9"
        opacity="1"/>
      <!--
      <Label  id="description" font="fontSmiley FrustratedmallBoldSystemFont"  text="PLACEHOLDER" />
      <Label  id="sublabel" font="fontSmiley FrustratedmallBoldSystemFont"  text="PLACEHOLDER" /> -->
  </children>

  <interface>
    <field id="width"       type="float" onChange="updateLayout"/>
    <field id="height"      type="float" onChange="updateLayout"/>
    <field id="itemContent" type="node" onChange="itemContentChanged" />
    <field id="labelColor" type="string" alias="label.color"/>
  </interface>

  <script type="text/brightscript">
    <![CDATA[
      sub Init()
        m.Poster = m.top.findNode("poster")
        m.Overlay = m.top.findNode("overlay")
        m.OverlayIcon = m.top.findNode("overlayicon")
        m.Label = m.top.findNode("label")
        m.Progress = m.top.findNode("progress")
        m.ProgressBg = m.top.findNode("progressbg")
      end sub

      sub itemContentChanged()
        m.Label.text = m.top.itemcontent.Title
        m.Overlay.visible = m.top.itemcontent.showRestrictedToDenmark
        m.OverlayIcon.visible = m.top.itemcontent.showRestrictedToDenmark
	if m.top.itemContent.Length > 0 and m.top.itemContent.lastPositionSec > 0 then
	  m.Progress.height = 9
	  m.ProgressBg.height = 9
	else
	  m.Progress.height = 0
	  m.ProgressBg.height = 0
	end if
        updateLayout()
        m.Poster.uri = m.top.itemContent.sdposterurl
      end sub

      sub updateLayout()
        height = m.top.height
        width = m.top.width
        if height > 0 And width > 0 then
          m.Poster.width = width
          m.Poster.height = height                                 <==== This is where the channel crashes
          m.Overlay.width = width
          m.Overlay.height = height
          m.OverlayIcon.translation = [(width - 201) / 2, (height - 150) / 2]
          m.Label.width = width - 18
          m.Label.translation = [9, height + 15]
          m.Progress.translation = [0, height - m.Progress.height]
	  if m.top.itemContent <> invalid and m.top.itemContent.Length > 0 and m.top.itemContent.lastPositionSec > 0 then
            m.Progress.width = m.top.itemContent.lastPositionSec / m.top.itemContent.Length * m.top.width
	  end if
          m.ProgressBg.translation = [m.Progress.width, height - m.Progress.height]
          m.ProgressBg.width = width - m.Progress.width
        end if
      end sub
    ]]>
  </script>

</component>

 

0 Kudos
6 Replies
speechles
Level 10

Re: Channel crash upon assigning to Poster.height

<field id="width" type="float" onChange="updateLayout"/>
<field id="height" type="float" onChange="updateLayout"/>

Now you will have a problem. Both width and height lead to the same place. That place must know when width is sent that height will not be populated yet. You never set a "default" of 0 in the above.

 

You either have to train that "updateLayout" (subroutine) to know when width is sent that height is coming next and always set width before setting height.

 

....or do like below and it will work the way you have it allowing width and height to be set in any order because you have this...

if height > 0 And width > 0 then

since you did the above it allows setting them in any order but you forgot this part below...

<field id="width" type="float" value="0" alwaysNotify="true" onChange="updateLayout"/>
<field id="height" type="float" value="0" alwaysNotify="true" onChange="updateLayout"/>

Then it will work like you think it should. Use "alwaysNotify" to make sure that if you have changed Uri  and the width/height are the same it still updates your progress indicators.

The line that errors is actually this line:
if height > 0 And width > 0 then

height will initially be invalid because you set width first. If you set height first width will be initially invalid. Once again because no "default" value was set. When you set a default value in the field it will prevent the leaking of invalid into your evaluations.

 

0 Kudos
JesUltra
Level 7

Re: Channel crash upon assigning to Poster.height

Thank you for your input.  However, I believe that something more is going on, let me explain.

As far as I can tell, "float" properties of SGNodes default to 0, if no default value is set, and they cannot take any "invalid" value, only numerical values.

I confirmed the first part, by inserting the following line just above the first if statement in updateLayout:

print "CItem.updateLayout("; width; ", "; height; ")"

And then I get output like this:

CItem.updateLayout( 0 , 0 )
CItem.updateLayout( 384 , 0 )
CItem.updateLayout( 384 , 576 )

So there we can see that the properties default to zero, even in the absence of an explicit default in the XML.

Also, according to your explanation, I would expect to see error every time, at it will always be the case that one of the two properties is set first, causing an invocation of updateLayout() with the other having the default value.  Yet, I see no error in the vast majority of cases, in fact, I have never seen the error occur while developing, only though Roku's dashboard.

I do not believe that the if statement is what is generating an error, as I trust Roku's line number indication in the dashboard, though that points oddly at the height assignment.

Your comment about updating progress is interesting, but as far as I can tell, I get an invocation of itemContentChanged, in case any property of the content node is changed, which covers progress (also in cases where width and height are not even re-set to same value.) 

0 Kudos
speechles
Level 10

Re: Channel crash upon assigning to Poster.height

Sub UpdateLayout()

    If m.top.height > 0 and m.top.width > 0 Then
        ' Only update on change
        If m.top.height <> m.wasHeight Or m.top.width <> m.wasWidth Then
            m.wasHeight = m.top.height
            m.wasWidth = m.top.width
            ChangeLayout(m.top.width, m.top.height)
        End If
    End if

End Sub

Sub ChangeLayout(width,height)

... rest of your code here using width and height as if they are pre-populated ...

End Sub

Do your changes inside the ChangeLayout. Then everything should work as expected and you can pass them off as expected values.

If you still have the problem (inside ChangeLayout) after this it has to be something else in your code and the problem shows itself here but it really is endemic of a problem somewhere else.

0 Kudos
JesUltra
Level 7

Re: Channel crash upon assigning to Poster.height

I am sure it must have relation to something elsewhere in the code.  I am searching my brain though, for how it could possibly happen that one assignment of m.Poster.width with a positive value can succeed, but at the very next line, assignment of m.Poster.height also with a positive value could possibly fail.

The only ways I can see, is that if the Poster object does not have a height property (not possibe), that something has re-assigned the m.Poster variable to refer to another object (multithreading), or that the Poster object has been garbage collected, (though that seems unlikely as the enclosing CItem must have a reference to keep the Poster alive.)

None of the above really makes sense to me, though, which is why I am asking here.

0 Kudos
JesUltra
Level 7

Re: Channel crash upon assigning to Poster.height

I ran a "monkey test", that is, using ECP to simulate an endless sequence of random keypresses, and saw something interesting, copy below.

The first lines are ordinary output from my channel, "Exiting channel" being printed from appMain.brs, as it leaves its event loop, and will terminate.

A number of interesting observations.

  1. The error happens after AppExitComplete beacon.
  2. Garbled output in the BrightScript console indicates that debugger and beacon messages are emitted at the same time.
  3. Printing the value of the build-in associative array "m" shows that it does not contain entries for "Poster" or any of the other fields assigned from the Init() method, only "top" and "global".
  4. The thread listing shows only one entry (the render thread), my application main thread and background networking thread have both terminated at this point.

I hope that someone can educate me about how to avoid the render thread continuing to execute code after the application main thread having already exited.

As you can see in appMain.brs below, my scene object uses a property "appExit" to signal that the channel should close, as alternative to msg.isScreenClosed().  Based on my other code, I believe that the appExit property was the trigger in this case.  Though if my memory serves, I have observed similar crashes in CItem.xml before I introduced the appExit property.

Trace:/tmp/components/VideoScene.brs:2089
Trace:/tmp/components/VideoScene.brs:2094
Trace:/tmp/components/VideoScene.brs:2268
Exiting channel
04-07 22:55:51.241 [beacon.signal] |AppExitInitiate -----------> TimeBase(25292 ms)
04-07 22:55:51.253 [beacon.signal] |AppExitComplete -----------> Duration(19 ms), 287.53 KiP
04-07 22:55:51.254 [beacon.header] __________________________________________

[bightScript Mi0c4-r0o D7e b2u2g:g5e5:5r1.2.54 
Enter any BrigeachotSncrip.tre psotatremetn]t,  |ApdpLaeubnucgh IcnoimtmiaantdesB  ----o-r- -HE-L-P> T.ime

Sauses(pen0din g thmreads.s..)

04-07 22:55:51.254 [beacon.report] |AppLaunchComplete ---------> Duration(2166 ms), 224.29 KiP
Thread selected:  0*   pkg:/components/CItem.xml(51)           m.Label.text = m.top.itemcontent.Title

04-07 22:55:51.254 [beacon.report] |AppLoadInitiate -----------> TimeBase(762 ms)
04-07 22:55:51.255 [beacon.report] |AppSplashInitiate ---------> TimeBase(1 ms)
04-07 22:55:51.255 [beacon.report] |AppSplashComplete ---------> Duration(664 ms)
04-07 22:55:51.255 [beacon.report] |AppExitInitiate -----------> TimeBase(25292 ms)
04-07 22:55:51.255 [beacon.report] |AppExitComplete -C-urr-e-n-t- -F-u-n-c-t>i oanu:r
tion(19 ms), 287.53 KiP
050:  04-07 22:55:51.255 [beacon.footer] __________________________________________
      sub itemContentChanged()
051:*         m.Label.text = m.top.itemcontent.Title
052:          m.Overlay.visible = m.top.itemcontent.showRestrictedToDenmark
053:          m.OverlayIcon.visible = m.top.itemcontent.showRestrictedToDenmark
054:    if m.top.itemContent.Length > 0 and m.top.itemContent.lastPositionSec > 0 then
055:      m.Progress.height = 9
Source Digest(s): 
pkg: dev 2.6.64 e9bdf8abe4162a3ea604682342620430 DRTV, Danish National TV

Invalid value for left-side of expression. (runtime error &he4) in pkg:/components/CItem.xml(51)
Backtrace:
#0  Function itemcontentchanged() As Void
   file/line: pkg:/components/CItem.xml(52)
Local Variables:
global           Interface:ifGlobal
m                roAssociativeArray refcnt=2 count:2
Threads:
ID    Location                                Source Code
 0*   pkg:/components/CItem.xml(51)           m.Label.text = m.top.itemcontent.Title
  *selected

Brightscript Debugger> 04-07 22:55:54.389 [ui.frm.plugin.running.enter] Entering PLUGIN_RUNNING for dev
04-07 22:55:54.390 [beacon.signal] |AppLaunchInitiate ---------> TimeBase(0 ms)

Brightscript Debugger> print m
<Component: roAssociativeArray> =
{
    global: <Component: roSGNode:Node>
    top: <Component: roSGNode:CItem>
}

Brightscript Debugger> print m.top.itemcontent.Title
Den tynde blå linje | Fiktionsserie | 1 SÆSON

appMain.brs

sub Main(input as Dynamic)
  print "############################"
  print "#  Start of DR TV Channel  #"
  print "############################"

  m.port = CreateObject("roMessagePort")

  screen = CreateObject("roSGScreen")
  screen.setMessagePort(m.port)

  roInput = CreateObject("roInput")
  roInput.SetMessagePort(m.port)

  m.global = screen.getGlobalNode()
  m.global.id = "GlobalNode"
  m.global.addFields({"experiments": input.experiments})
  scene = screen.CreateScene("VideoScene")

  ' Add deep linking support here. Input is an associative array containing
  ' parameters that the client defines. Examples include "options, contentID, etc."
  ' See guide here: https://sdkdocs.roku.com/display/sdkdoc/External+Control+Guide
  ' For example, if a user clicks on an ad for a movie that your app provides,
  ' you will have mapped that movie to a contentID and you can parse that ID
  ' out from the input parameter here.
  ' Call the service provider API to look up
  ' the content details, or right data from feed for id
  if input <> invalid then
    print "input: "; input
    if input.reason <> invalid then
      if input.reason = "ad" then
        print "Channel launched from ad click"
        'do ad stuff here
      end if
    end if
    if input.contentID <> invalid then
      print "contentID is: " + input.contentID
      scene.commandInput = input
    else
      scene.commandInput = input
    end if
  end if

  'Deep link params
  screen.show()
  scene.observeField("appExit", m.port)

  continue = true
  while (continue)
    msg = wait(0, m.port)
    msgType = type(msg)
    if msgType = "Invalid" then
    else if msgType = "roSGScreenEvent" then
      print "Exiting channel"
      if msg.isScreenClosed() then continue = false
    else if msgType = "roSGNodeEvent" then
      print "Exiting channel"
      if msg.getField() = "appExit" then continue = false
    else if msgType = "roInputEvent" then
      if msg.IsInput() then
	inputData = msg.GetInfo()
	print "input: "; inputData
	if inputData.DoesExist("mediaType") and inputData.DoesExist("contentID") then
	  deeplink = {
	    contentId: inputData.contentID
	    mediaType: inputData.mediaType
	  }
	  print "got input deeplink= "; deeplink
	  scene.commandInput = deeplink
	end if
      end if
    else
      print "Unrecognized event: " + msgType
    end if
  end while
end sub
0 Kudos
JesUltra
Level 7

Re: Channel crash upon assigning to Poster.height

I forgot to mention two other observations in the previous post.

My "monkey test" script is designed to recognize the "Exiting channel" message, and upon seeing that message, it invokes the ECP command http://...:8060/launch/dev, in order to start the channel again, and continue the test.

In this particular instance, my script got a 5xx HTTP code from the ECP command, which along with the garbled log output, suggests to me that this behavior could in part be a bug or shortcoming in the Roku OS itself.

Also, as I typed "quit" in the debugging prompt above, I saw my channel continuing to initialize, for a while making me unsure whether I was debugging the "old" instance in the process of closing, or the "new" instance in the process of initializing.  The fact that there was no main thread, together with the fact that the object had actual string data originating from my server, convinces me that I was debugging the "old" instance in some zombie form, though.

0 Kudos