Skip to content

VIP9B: Extend vmod object scopes

Nils Goroll edited this page Dec 9, 2019 · 6 revisions

This proposal is the outcome of collaboration between Dridi and slink, influenced by reza and others:

Synopsis

We propose, in more detail than prior work, how vmod objects with additional scopes could be defined, implemented and managed.

We also propose new vcl subroutines for object initialization and to solve the requirement to copy or move objects from the client to the backend side.

Why

The motivation is the discussion around the introduction of native variables to VCL and the extension of object scopes (PR #3140, VIP9)

We believe that this proposal shows ways how to achieve support for additional object scopes in a backwards compatible, safe and efficient manner.

In combination with VIP28 (designated vmod object methods), this is also intended as a basis for implementing variables in VCL.

How

Scopes

We propose the following scopes for vmod objects:

  • vcl aka global

    same as the only currently available scope

  • client

    same as PRIV_TASK on the client side

  • backend

    same as PRIV_TASK on the backend side

  • top

    same as PRIV_TOP (only on the client side)

  • local

    like a local variable in C, scope is the current VCL sub

New vcl initialization subs

We propose the following new builtin subs

  • sub vcl_top_init {}

    called once per client request at esi_level 0 before vcl_recv

    used for top object initialization, but not limited to it

    any non-local object constructed here will have top scope

  • sub vcl_client_init {}

    called once per client request before vcl_recv

    used for client object initialization, but not limited to it

    any non-local object constructed here will have client scope

  • vcl_backend_init {}

    called once per backend request halfway between the client and the backend side

    here, both the client and backend scope objects are available (besides req and bereq), so any copy or move operations between the client and backend side can take place.

    any non-local object constructed here will have backend scope

local scope

Any object constructed in a non-init VCL sub will have local scope and be usable only within that sub (something like VIP2 might allow to pass locally scoped objects down the stack).

VCC will provide a pointer to local objects, but no space.

To allow for efficient allocations, VCC will extend support for the aws (stack workspace): in each VCL sub, the aws will get snapshot in the preamble and restored before each return, allowing vmods to make allocations from the aws which follow the regular stack layout.

For failing aws allocations, vmods should fall back to the regular task workspace.

local scope vmod methods will need to ensure that any return values have at least PRIV_TASK scope as before.

Edit 2019-11-23:

To clearly mark the limited scope of local objects and to support them also in the *_init {} subs, local objects are defined by the local keyword instead of new.

Edit 2019-12-01:

local need to be passed to the constructor, for example as a flag.

NULL handling

Currently, VCL loading fails if any declared objects remain uninitialized after vcl_init {} returns, unless the constructor is declared with NULL_OK in the vcc file.

We suggest to extend this concept to the new subs in that a VRT_fail() error will be triggered at runtime for uninitalized client/backend/top/scoped objects unless the constructor is declared with NULL_OK.

For use with local scope, vmods are required to provide NULL_OK support.

Note that NULL refers here to the object, not the value of the object. In other words, a NULL object and a method returning NULL for access to an object (as it might be the case after an unset) are different things.

VCC declaration

By default, $Object semantics remain unchanged for vcl scope.

Vmods supporting additional scopes make a $Scope declarations in the VCC file as:

$Scope [any, vcl, top, client, backend, local, vcl_recv, vcl_deliver, ....]

This is a variant of the VIP4 $Restrict keyword proposal. We would also include the remaining functionality of VIP4:

  • The default $Scope is any, except for $Object, where it is vcl
  • $Method can be $Scoped or $Restricted to a subset of the $Object's $Scope
  • We have no strong preference in naming, $Scope is probably a better fit

C Interface

vmod object constructors determine the scope from the method member of the VRT_CTX.

Macros will be provided as convenience functions, for example:

SCOPE_VCL(ctx)
SCOPE_CLIENT(ctx)
SCOPE_BACKEND(ctx)
SCOPE_TOP(ctx)
SCOPE_LOCAL(ctx)

Examples

# MOCKUP VCL which is intended to demo VIP9B, but also contains syntax
# ideas from VIP28 - notice that VIP9B is viable without those, just
# the syntax would look different, e.g.:
#
# esi_fallback.set(tmp.get())
#
# instead of
#
# set esi_fallback = tmp


################################################################################
## TOP scope example

import file;	# sub vcl_top_init {}

sub vcl_init {
    new fallback = file.reader("fallback.txt");
}

sub vcl_top_init {
    local tmp = var.string();

    # constructing a string on the stack (aws) reduces workspace
    # footprint, but tmp is really only intended as a demo here
    set tmp = "<html>";
    set tmp += fallback.get();
    set tmp += "<br>Generated " + now + " for " + client.ip + "</html>";

    # the var.string() getter returns the value on the workspace, construct
    # a blob to (potentially) re-use in every esi subreq
    new esi_fallback = var.blob();
    set esi_fallback = tmp;
}
# for any initialization error, we would VRT_fail the request at this point
# (before vcl_recv is called)

sub vcl_deliver {
    if (resp.status == 503 && req.esi_level > 0) {
        set resp.body = esi_fallback;
    }
}

################################################################################
## client / backend scope example

import directors;

backend foo None;
backend var None;

sub vcl_client_init {
    new shard = directors.shard();
    shard += foo;
    shard += bar;
    shard.reconfigure();
}

sub vcl_recv {
    set req.backend_hint = shard.backend(by=KEY, key=1234);
}

sub vcl_backendend_init {
    # creates a new backend object on the backend side from the client
    # side object
    new beshard = shard.inherit();
}

sub vcl_backend_fetch {
    set bereq.backend = beshard;
}
Clone this wiki locally