Using libunbound from other languages

Hello,

I'm struggling with the following use case and I'm not sure the solution lays in
extending my code or bug Woute^H the unbound developers with requests.

In this case I'm talking about using libunbound in Go and the locking issues
that can occur with wrt to the crypto library that is used.

In Go you do not have control over the amount of OS threads the runtime uses.
Go uses goroutines and N of those routines are mapped to an OS thread at any moment
in time.

So if I'm using libunbound in my Go code it would be a good thing to tell OpenSSL
that it needs to use locking because I might use threads. However, with (lib)unbound:

* I cannot (easily) detect at runtime which crypto library unbound uses
* I do not link with a crypto library myself directly, so setting up the locking
  feels a bit strange
* Also there is not thread_id in Go, so I cannot tell openssl this

My feeling is that 'ub_openssl_lock_init' (in util/net_help.c) should be moved
to libunbound, so that I can "just" call it from Go when I setup the library for Go use, but
I might be wrong.

How do other languages wrapping libunbound deal with this?

- Grtz,

For this sort of scenario, you want Go's runtime.LockOSThread().

-Phil

[ Quoting <unbound-users+phil@spodhu> in "Re: [Unbound-users] Using libunboun..." ]

You don't need it. You create a go-routine which uses channels for
asking for some DNS and getting the response back. Something like this
(untested):

  type ResolveResult struct {
    // whatever
    Error error
  }
  type ResolveResultAndError struct {
    Result *ResolveResult
    Error error
  }
  type ResolveRequest struct {
    Label string
    DNSType string
    Response chan ResolveResult
  }

  var GlobalResolver chan *ResolveRequest

  func QueryDNS(label, typ string) (ResolveResult, error) {
    inbox := make(chan ResolveResultAndError, 1)
    GlobalResolver <- &ResolveRequest{
      Label: label,
      DNSType: type,
      Response: inbox,
    }
    response := <-inbox
    if response.Error != nil {
      return nil, response.Error
    }
    return response.Result, nil
  }

  func InitDNS() {
    GlobalResolver := make(chan *ResolveRequest, 10)
    oneResolverThread := func() {
      runtime.LockOSThread()
      defer runtime.UnlockOSThread()
      // do Unbound setup
      for {
        switch {
        case req := <-GlobalResolver:
          // initiate request into Unbound
        case resp := <-somethingGettingUnboundResponses
          // send an result/error combo back down req.Response
        }
      }
    }
    go oneResolverThread() // repeat N times (see below)
  }

You can then have a pool of Unbound clients, for as many concurrent
resolver OS threads as you want, all reading from the same
GlobalResolver channel, giving you automatic load-balancing across the
resolver threads. The response goes back to the inbox supplied. If the
Unbound API is being used synchronously, then you don't even have the
inner switch in the routine, just get the client request, handle it,
respond.

Synchronous with N OS threads (no Go switch) and C.ub_resolve() should
get things going, since the callbacks for async will basically need to
be handled carefully, since if memory serves Go doesn't expose channel
write to the C layer. Or am I wrong?

-Phil

[ Quoting <unbound-users+phil@spodhu> in "Re: [Unbound-users] Using libunboun..." ]

> Hmmmm, that would certainly help (and maybe hurt performance), but then I still
> don't have the proper thread locking in place....

You don't need it. You create a go-routine which uses channels for

If you say, 'You don't need it", of what locking and where do you talk about?
The Go code can be relative lock free, but why can I get away with not telling
OpenSSL (or NSS) that it is going to get called from threaded code?

      runtime.LockOSThread()
      defer runtime.UnlockOSThread()
      // do Unbound setup

This would be the point to "tell OpenSSL/NSS that it is going to get called from
threaded code", No?

Synchronous with N OS threads (no Go switch) and C.ub_resolve() should
get things going, since the callbacks for async will basically need to
be handled carefully, since if memory serves Go doesn't expose channel
write to the C layer. Or am I wrong?

You may be right, but I think you are talking about stuff one layer higher than
I am.

- Grtz,

If you say, 'You don't need it", of what locking and where do you talk about?
The Go code can be relative lock free, but why can I get away with not telling
OpenSSL (or NSS) that it is going to get called from threaded code?

(I'm ignoring libraries that are actively thread hostile, by doing
things like repointing shared resources from outside the library, such
as stdio; OpenSSL and friends don't do this.)

From a perspective of thread locking within a library, as long as it is

only ever entered into from one thread, the existence of other threads
is irrelevant. You don't need to lock against calls that never happen.
You do need to be very sure that you're only called from one thread.

Of course, like an idiot in my second post with code samples, I went and
then suggested initialising multiple threads for concurrency,
undermining the whole reason for doing this. Duh. Sorry. :frowning: Yes, if
you do setup multiple threads which can enter libunbound, you'd need to
init the SSL library appropriately. Do you need to do this? If only
one thread of DNS resolution is sufficient, then my point stands.

I was trying to offer a way forward using existing versions of Unbound
deployed today, rather than a dependency upon a new library version.
You could have one thread today, and a simple cgo-exported "is this
library new enough to let me init crypto for multiple threads" predicate
which you can use to decide whether or not to permit spawning more than
one lookup thread.

Assuming just one thread entering Unbound, you do still need to need to
ensure you build the library for threaded modes of operation, because
that will ensure that errno is a thread-safe wrapper instead of a shared
global. That's library build-time, rather than invocation.

> runtime.LockOSThread()
> defer runtime.UnlockOSThread()
> // do Unbound setup

This would be the point to "tell OpenSSL/NSS that it is going to get called from
threaded code", No?

That's the function potentially called for multiple resolution threads,
so you'd need a sync.Once object you can call the .Do() method on, if
doing it there and invoking more that once.

But yes.

Looking more closely, and thinking, I realise you might be working on a
generic unbound wrapper for Go, so you're not asserting the app won't
use OpenSSL, only that your own library is not doing so? In which case,
I misinterpreted the impact of your second bullet point, sorry.

And in which case, I see the problem.

-Phil

[ Quoting <unbound-users+phil@spodhu> in "Re: [Unbound-users] Using libunboun..." ]

> OpenSSL (or NSS) that it is going to get called from threaded code?
you do setup multiple threads which can enter libunbound, you'd need to
init the SSL library appropriately. Do you need to do this? If only
one thread of DNS resolution is sufficient, then my point stands.

That's my point, I also think I need this. I don't really have (or want)
control over the thread creation in my Go -> unbound code. (Stuff is
already working see github.com/miekg/unbound)

You could have one thread today, and a simple cgo-exported "is this
library new enough to let me init crypto for multiple threads" predicate
which you can use to decide whether or not to permit spawning more than
one lookup thread.

Yeah, making that work transparant for older versions is a challenge. See
the current hack for a new member that was added in unbound 1.4.19:
https://github.com/miekg/unbound/blob/master/unbound.go#L47

Looking more closely, and thinking, I realise you might be working on a
generic unbound wrapper for Go, so you're not asserting the app won't
use OpenSSL, only that your own library is not doing so? In which case,
I misinterpreted the impact of your second bullet point, sorry.

I'm sorry I didn't make that (as I have running code) clear enough, but
multiple unbound.New()'s should be possible and I would even say
encouraged .

And in which case, I see the problem.

Hmmpff :slight_smile:

- Grtz,

[ Quoting <unbound-users+phil@spodhu> in "Re: [Unbound-users] Using libunboun..." ]