/tinyletter

The Programs of a Very Spooky Week

This Week’s Program: Oct 9 - Oct 13

It is Friday the 13th! The spookiest, scariest thing about this is how unseasonably warm this October has been in NYC. I am a creature of autumn weather and there has been far too little fall this fall.

Last week, I missed an important milestone!

Two years ago, on October 2, 2015, I sent out the first This Week’s Program email! Happy belated two year anniversary to this Tinyletter!

The stats:

  • I’ve sent 105 emails (including this one).
  • As of this writing I have 100 subscribers, including me. That is 4 more subscribers than last year. I think I need some growth hacking. If you subscribe and enjoy reading This Week’s Program, maybe tell your programmer friends!

Over the course of all my Tinyletters I’ve written about explorations in: CoffeeScript, Emacs Lisp, Ruby, CSS, Clojure, JavaScript, Bash, and Elm. This year I’ve written about working with D3.js, ECMAScript 6, Ansible, and my current project, Overscan: a curious blend of Racket and C.

That’s so much content. Thank you, friends, for opening your inboxes to me for the past two years.

Now, on to the spooky code I’ve written.

This week, I’d like to focus on one bug — or rather, some unexpected behavior that I encountered when working through building out the GStreamer code.

a66b038b8dddf68fa9902bce930be20a2c95c559

Let’s take a look at this GStreamer C function: gst_element_get_state ().

This function is defined as so:

GstStateChangeReturn
gst_element_get_state (GstElement *element,
                       GstState *state,
                       GstState *pending,
                       GstClockTime timeout);

The first argument is a pointer to an Element. Through the lens of GObject Introspection, this is the self argument. My introspection library is smart enough so that you don’t need to include it. When mixed through all the layers of Introspection, you can consider this the get_state method of the Element class, and the first argument is passed implicitly when invoking the method.

The next two arguments, state and pending, are pointers to a value of the State enum. These pointers are used for the purposes of pass-by-reference mutation. I’ve written about this a bunch in past newsletters. GObject Introspection marks arguments to functions with a direction: in, inout, or out. The inout and out arguments are used for the purposes of pass-by-reference: you pass in a pointer, and when the function returns, that pointer will be pointing to some new value.

The final argument is a ClockTime (an alias to an unsigned integer) to be used as a timeout to this function.

This function returns a StateChangeReturn value.

When you set the state of an element, the response you’ll usually get back is a StateChangeReturn that might tell you async — meaning, the state change is happening asynchronously. When you call get_state, the return result tells you if the last state change you made was successful or is still processing, but the true data you’re probably interested in comes from the two state pointers that you passed by reference: The first pointer tells you what state the element is currently in, and the second tells you what state the element is moving to if the state change hasn’t happened yet. The whole of the information this function returns is in these three values.

This function call was just not working with my introspection code. The commit above fixed this.

What was happening

In my GObject Introspection binding code, every introspected function is represented as a gi-function struct, which can be called as a procedure. The procedure does some clever things around arity. Namely, any argument that is decorated as an out argument should not be passed in — the introspection code will malloc a pointer of the appropriate size, set it to NULL, and pass that in. All outbound pointers are de-referenced and returned along with the return value of the function, because Racket is baller and has multiple return values, which are a great way to represent exactly the kind of thing that get_state is doing.

Here’s the relevant chunk of the before code in this implementation:

(define args+values
        (let* ([arglength (length args)]
               [arguments (if (and method? (pair? arguments))
                              (cdr arguments)
                              arguments)]
               [vals (append arguments (make-list (- arglength (length arguments)) #f))])
          (map (lambda (arg val)
                 (cons arg
                       (if (eq? (gi-arg-direction arg) 'out)
                           (ctype->_gi-argument _pointer
                                                (gi-type-malloc (gi-arg-type arg)))
                           (arg val))))
               args
               vals)))

This args+values definition forms a list of pairs of arguments anticipated by the underlying C function and the values that correspond to those arguments. In that initial let* statement you’ll see a reference to args and arguments. This is bad naming on my part: args are a list of the introspected arguments of the C function; its formal parameters. arguments are the list of values passed in to the Racket function.

Here’s where the bug came from: where the length of the args list and the arguments list didn’t match, the vals just appended NULL pointers until they did. This code makes the assumption that out arguments will always appear as the last arguments in the parameter list. That is incorrect!

Here’s the updated code:

(define-values (args+values vals)
        (for/fold ([res null]
                   [vals (if (and method? (pair? arguments))
                             (cdr arguments)
                             arguments)])
                  ([arg (in-list args)])
          (values (reverse (cons
                            (cons arg
                                  (if (eq? (gi-arg-direction arg) 'out)
                                      (ctype->_gi-argument _pointer
                                                           (gi-type-malloc (gi-arg-type arg)))
                                      (arg (car vals))))
                            res))
                  (if (eq? (gi-arg-direction arg) 'out) vals (cdr vals)))))

Here, instead of appending NULL values to the end of the arguments list, and then mapping over the two lists, I use for/fold to fold (aka reduce) the lists of arguments and args so that when an arg is encountered that is an out, the arguments list is ignored and a NULL pointer is put in that place for the value.

As you can see, I use quite a bit of cons to get this set up. cons is the big-fucking-hammer function for lists in Racket/Scheme: it is how all other list functions are wrought, and it’s also inelegant and clumsy. I could not find a higher-level list function that would help me construct this list quite the same way.

But this fixed my problem and now I can do this:

1efb6d5669701572509a5d54c6c98789d0c793ff

Here’s the implementation and contract for get_state on an element%. I made the timeout optional, providing a sensible default.

[get-state
    (->*m ()
          (clock-time?)
          (values (gi-enum-value/c state-change-return)
                  (gi-enum-value/c state)
                  (gi-enum-value/c state)))]

So the get-state method takes one optional argument: a clock-time?, and returns three values: the state-change-return, the current state, and pending state.

Easy peasy.

The bulk of this week’s commits were implementing more GStreamer functionality and trying to make a really solid GStreamer binding for Racket. The tools I created for ffi/unsafe/introspection are really paying off. I’ll discuss more of these changes next week, but wanted to highlight one more thing:

3459c1e9ab50c20045291d8a4211206ae89054e3

I remove all naming conflicts between racket/class and ffi/unsafe/introspection. Now they can coexist peacefully!

Next week, I’ll be cleaning up the gstreamer module and writing a bunch of documentation for it!

Today is the annual Harry’s chili cookoff, and I’m going to go eat some chili!

Have a spoo-o-ooky Friday the 13th and … look out behind you!

🏒 Mark 🔪