RFC: talloc contexts / automatic free at select loop

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/OpenBSC@lists.osmocom.org/.

Neels Hofmeyr nhofmeyr at sysmocom.de
Wed Mar 20 18:01:48 UTC 2019


On Wed, Mar 20, 2019 at 10:52:46AM +0100, Harald Welte wrote:
> To Neels' disappointment, this is not all automatic.

Flabbergasted is the word. I thought things would get easier instead of more
convoluted and bloated.

To me clearly the drawbacks of making it explicit are humungous and cumbersome
while avoiding fixing hidden bugs plus keeping all static buffers around.
Making it implicit has only positive effects AFAICT.

My bottom line is that I absolutely full on don't understand why we would
volunarily burden ourselves with this huge patch writing overhead and code
bloat, now and into the future. We could so trivially collapse all of it.

My single question is, why. What is the reason for accepting this huge tail?
(ง •̀_•́)ง -- why?

If you're interested, here are my opinions in detail...

> a) have to call the _c suffix of the respective function, e.g. osmo_hexdump_c()
>    instead of osmo_hexdump() with one extra initial argument:
>    OTC_SELECT. There's also msgb_alloc_c(), msgb_copy_c() and the like,
>    allowing msgb allocation from a context of your choice, as opposed
>    to the library-internal msgb_tall_ctx that we had so far.

These are the problems I have with this:

- complexity: constantly have to take care to choose a talloc context, mostly
  burdens writing log statements:

  - single-threaded program: Is there a global pointer to a main()
    volatile talloc context we can always use? Ok, OTC_SELECT is passed
    everywhere.

  - multi-threaded program: Is OTC_SELECT implicitly distinct per thread? Then
    multi-threaded programs pass OTC_SELECT everywhere.

  - So everyone always passes OTC_SELECT everywhere, then what's the use of the
    ctx argument.
    In the super rare cases where something like osmo_plmn_name() wants to
    allocate from a different context, I would just do a talloc_strcpy() to the
    other context.

- coding style:

  - bloat: we need to pass the same ctx argument to all those name() functions.
    That makes it longer, harder to read, more to type.

    With the implicit solution:

       LOGP(DMSC, LOGL_ERROR, "Peer's PLMN has changed: was %s, now is %s. MSC's PLMN is %s\n",
    	osmo_plmn_name(&parsed_plmn), osmo_plmn_name(&vsub->cgi.lai.plmn),
    	osmo_plmn_name(&net->plmn));

    With the explicit solution:

       LOGP(DMSC, LOGL_ERROR, "Peer's PLMN has changed: was %s, now is %s. MSC's PLMN is %s\n",
    	osmo_plmn_name_c(OTC_SELECT, &parsed_plmn), osmo_plmn_name_c(OTC_SELECT, &vsub->cgi.lai.plmn),
    	osmo_plmn_name_c(OTC_SELECT, &net->plmn));

    Would irritate me a lot. So I would usually just use the old ones instead.

  - complexity: then we are again in the situation where, when writing new code,
    I have to consciously decide where to allocate the strings from. That's
    something I wanted to get rid of. It would be so relieving to just know:
    "osmo_plmn_name() is safe" and just spam on using it without extra args,
    without the first call being osmo_plmn_name() and the second call
    osmo_plmn_name_c(OTC_SELECT, plmn), and then oh damn, I forgot that the
    third one also has to be _c, or some indirectly called function also fails
    to use the _c variant... If there is just one osmo_plmn_name() I cannot
    possibly do anything wrong, no hidden bugs anywhere, by design.

  - less arguments, less signatures: The reason why we have value_string[]
    shortcuts like osmo_gsup_message_type_name() is so that we can save an
    extra argument (the names[] array) when writing log statements. We didn't
    want to add so many bin symbols to the API just for that, so we agreed on
    using static inline functions in the .h files. Fair enough. So each
    value_string[] definition currently has a tail of such a static inline
    function attached. With this proposal we're re-adding a second static
    inline _c variant to each and every value_string[] tail, with another
    function arg again added to the _c function signature.

    We will also need a get_value_string_c() function because for unknown
    values it uses a static buffer to return a number string. We can so
    trivially avoid all of this mess.

  - less bugs: get_value_string() is a prime example for wanting to fix things
    implicitly. If a log statement uses two get_value_string() based
    implementations, even for completely different names, then it would print
    nonsense if both hit an undefined value.
    Pseudo code example:
       LOGP(DRSL, LOGL_ERROR, "Unknown message: proto %s message %s\n",
            proto_name(h->proto), msg_type_name(h->msg_type));
    If there is both an unkown proto 42 and msg_type 23, then only one of the
    "unknown 42" result strings from get_value_string() survives. It would say
    "Unknown message: proto unknown (42) message unknown (42)"
    These are hidden bugs which we would completely eradicate by making
    distinct buffer use implicit. It is not always obvious which functions use
    get_value_string() in their implementation. So we should use _c functions
    always. So then why even have them as distinct signatures.

- static buffers: We have quite a number of static char[] around the code base,
  my idea was that we could get rid of those. With this proposal we have to
  keep them.

- API bloat:

  - This adds a lot of new API signatures to the existing ones. Both static
    inline for value_string[], and bin symbols in the library files for things
    like osmo_plmn_name_c().

  - Each future function needs a _c variant added to it. We may forget them,
    then more discussions on gerrit. More patches to fix that. I would rather
    avoid that by design.

I don't understand why you want to do it this way. What is the justification
for all this bloat, required patches now and future complexity in writing code?


> b) have to use osmo_select_main_ctx() instead of osmo_select_main().  This is
>    an arbitrary limitation for optimization that Pau requested, to make sure
>    applications that don't want this can avoid all the extra talloc+free at
>    every select iteration.

IMO that is the wrong place to hinge the argument.

- If nothing used the volatile context, nothing needs to be freed.
- Applications wanting to avoid dynamic allocations can avoid calling functions
  that allocate dynamically. Admitted, with my under-the-hood proposal even
  functions like osmo_plmn_name() would do dynamic allocations, but:
- In the real world, I don't know why you would want to avoid dynamic
  allocations. AFAICT the performance impact is basically non-existent. How
  else do we get away with dynamically allocating *all* of our msgb and even
  copying them around quite often? We also dynamically allocate user contexts,
  transaction structs, basically all llist entries, strings that objects
  reference,...
- And finally, if dynamic allocations are identified as a problem, then there
  are ways to avoid them: keep N "volatile" buffers around, re-use them across
  osmo_select() iterations without freeing in-between. A dedicated function to
  return volatile buffers could take care of that.
  I mean instead of passing a specific ctx, rather call a separate function
  osmo_volatile_buffer(size), so we can slip in an optimisation like that at
  any point in the future.
- Finally finally, optimisations can be done by talloc, the kernel and whatnot,
  and I'm fairly sure there is a lot of thought going into memory management
  and optimising dynamic allocations so that applications can completely skip
  this argument, which I propose we do.
- And triple finally, switch off debug logging to completely cut out almost all
  allocations for logging. If the performance argument holds, the improvement
  should be dramatic. So far a dramatic improvement is seen when switching off
  debug logging for low-level logging categories on a sysmobts, for osmo-bts
  and osmo-pcu, but that isn't related to dynamic allocations, much rather to
  plain fprintf() I/O -- which is much more perf critical than de/allocating.

> This is debatable, and we can make it automatic
> in osmo_select_main().

+1

> It's probably worth a benchmark how expensive that 'empty' allocation + free is.

If some (inline?) osmo_volatile_buffer() function creates such a talloc context
only on demand, then I don't see how this can possibly have any impact at all.

> However, I think that's a rather "OK" price to pay.  Converting the existing
> callers can be more or less done with "sed" (yes, spatch is of course better).

ugh really; I still argue that the API is then bloated, and more importantly
writing new code becomes more complex and more bloated as well.
I don't really want to live in that API.

> While introducing this feature, I also tried to address two other topics, which
> are not strictly related.  However, ignoring those two now would mean
> we'd have API/ABI breaks if we'd care about them in the future.  Hence I
> think we should resolve them right away:
> 
> 1) introduce another context: OTC_GLOBAL.  This is basically a library-internal
>    replacement for the "g_tall_ctx" that we typically generate as one of the
>    first things in every application.  You can use it anywhere where you'd want
>    to allocate something form the global talloc context

Nice.

Shouldn't the name start with OSMO_ though?

> 2) make those contexts thread-local.

That's pretty cool.

Ok, so we already have distinct volatile talloc contexts per thread, implicitly.
Then why would you still want to pass this context to each function call explicitly?

[[
Trying to read up on __thread, I noticed there are portability / C version
considerations. This macro, if we remove the windows bit, looks interesting:
https://stackoverflow.com/questions/18298280/how-to-declare-a-variable-as-thread-local-portably
i.e.

#ifndef thread_local
# if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__
#  define thread_local _Thread_local
# elif defined __GNUC__ || \
       defined __SUNPRO_C || \
       defined __xlC__
#  define thread_local __thread
# else
#  error "Cannot define thread_local"
# endif
#endif

?
]]

~N
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://lists.osmocom.org/pipermail/openbsc/attachments/20190320/244c6b0c/attachment.bin>


More information about the OpenBSC mailing list