]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Fix "context canceled" with partial
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 4 Mar 2023 17:08:29 +0000 (18:08 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 4 Mar 2023 20:29:05 +0000 (21:29 +0100)
Make sure the context used for timeouts isn't created based on the incoming
context, as we have cases where this can cancel the context prematurely.

Fixes #10789

lazy/init.go
lazy/init_test.go
resources/transform.go
tpl/partials/integration_test.go
tpl/partials/partials.go

index 4de2a83f74ff4407d3c88327372b16bb0e93575b..bfb9c4e0791ff92504bfdbf24f6b06a6786e6d7e 100644 (file)
@@ -180,14 +180,15 @@ func (ini *Init) checkDone() {
 }
 
 func (ini *Init) withTimeout(ctx context.Context, timeout time.Duration, f func(ctx context.Context) (any, error)) (any, error) {
-       ctx, cancel := context.WithTimeout(ctx, timeout)
+       // Create a new context with a timeout not connected to the incoming context.
+       waitCtx, cancel := context.WithTimeout(context.Background(), timeout)
        defer cancel()
        c := make(chan verr, 1)
 
        go func() {
                v, err := f(ctx)
                select {
-               case <-ctx.Done():
+               case <-waitCtx.Done():
                        return
                default:
                        c <- verr{v: v, err: err}
@@ -195,7 +196,7 @@ func (ini *Init) withTimeout(ctx context.Context, timeout time.Duration, f func(
        }()
 
        select {
-       case <-ctx.Done():
+       case <-waitCtx.Done():
                return nil, errors.New("timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.")
        case ve := <-c:
                return ve.v, ve.err
index 499ea2cce7b5b1b58eef38cf0068fc7ce6b72484..efc329d797e7f7863d0ade823b51476a98fc9824 100644 (file)
@@ -126,12 +126,6 @@ func TestInitAddWithTimeoutTimeout(t *testing.T) {
 
        init := New().AddWithTimeout(100*time.Millisecond, func(ctx context.Context) (any, error) {
                time.Sleep(500 * time.Millisecond)
-               select {
-               case <-ctx.Done():
-                       return nil, nil
-               default:
-               }
-               t.Fatal("slept")
                return nil, nil
        })
 
index 4ab51485e1bae2f723b0d45d6c7300d2a06bf315..fe438e366149c601e5d4a45bdd585f3b2c23daf8 100644 (file)
@@ -164,12 +164,12 @@ type resourceAdapter struct {
        *resourceAdapterInner
 }
 
-func (r *resourceAdapter) Content(context.Context) (any, error) {
+func (r *resourceAdapter) Content(ctx context.Context) (any, error) {
        r.init(false, true)
        if r.transformationsErr != nil {
                return nil, r.transformationsErr
        }
-       return r.target.Content(context.Background())
+       return r.target.Content(ctx)
 }
 
 func (r *resourceAdapter) Err() resource.ResourceError {
index fcebe6c05d836f565417a7d64303fd4111b1c4b8..3dbaf2ce41efbab58530bf0680af396cf19478bb 100644 (file)
@@ -324,3 +324,31 @@ timeout = '200ms'
        b.Assert(err.Error(), qt.Contains, "timed out")
 
 }
+
+// See Issue #10789
+func TestReturnExecuteFromTemplateInPartial(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- config.toml --
+baseURL = 'http://example.com/'
+-- layouts/index.html --
+{{ $r :=  partial "foo" }}
+FOO:{{ $r.Content }}
+-- layouts/partials/foo.html --
+{{ $r := §§{{ partial "bar" }}§§ | resources.FromString "bar.html" | resources.ExecuteAsTemplate "bar.html" . }}
+{{ return $r }}
+-- layouts/partials/bar.html --
+BAR
+  `
+
+       b := hugolib.NewIntegrationTestBuilder(
+               hugolib.IntegrationTestConfig{
+                       T:           t,
+                       TxtarString: files,
+               },
+       ).Build()
+
+       b.AssertFileContent("public/index.html", "OO:BAR")
+
+}
index d9a826aa41b7ee062dbe71c5bd4d159dd2e05c4c..26ce0f5c66de66cd5a41da5bc2271fa3b2e8c570 100644 (file)
@@ -129,7 +129,10 @@ func (ns *Namespace) Include(ctx context.Context, name string, contextList ...an
 }
 
 func (ns *Namespace) includWithTimeout(ctx context.Context, name string, dataList ...any) includeResult {
-       ctx, cancel := context.WithTimeout(ctx, ns.deps.Timeout)
+       // There are situation where the ctx we pass on to the partial lives longer than
+       // the partial itself. For example, when the partial returns the result from reosurces.ExecuteAsTemplate.
+       // Because of that, create a completely new context here.
+       timeoutCtx, cancel := context.WithTimeout(context.Background(), ns.deps.Timeout)
        defer cancel()
 
        res := make(chan includeResult, 1)
@@ -141,8 +144,8 @@ func (ns *Namespace) includWithTimeout(ctx context.Context, name string, dataLis
        select {
        case r := <-res:
                return r
-       case <-ctx.Done():
-               err := ctx.Err()
+       case <-timeoutCtx.Done():
+               err := timeoutCtx.Err()
                if err == context.DeadlineExceeded {
                        err = fmt.Errorf("partial %q timed out after %s. This is most likely due to infinite recursion. If this is just a slow template, you can try to increase the 'timeout' config setting.", name, ns.deps.Timeout)
                }