Fiber: 馃悰 Using fiber with LRU

Created on 12 Sep 2020  路  2Comments  路  Source: gofiber/fiber

Fiber version

v1.14.4

Issue description

  • This is not a normal bug report that I normally do
  • It is based on days and days of troubleshooting and finally come down to this pin-point
  • I.e., there are a huge amount of test that I did that I'm not going to include here, just the core issue
  • The conclusion might seems so absurd but this is the best I can do so far, in terms of narrowing things down
  • OK, before jumping into what the problem/conclusion is, let me begin with what I have been trying to do -- I have been trying to store some values (like recently visited items) in user cookies, and use the LRU algorithm to limit the cookie size. I.e., it involves fiber web server, fiber cookie handling and the LRU algorithm.
  • The problem is that the stored cookie result looks absurd -- it doesn't make sense.
  • After days and days of troubleshooting, eliminating everything especially the LRU algorithm being the problem, I finally concluded that the LRU algorithm plus the fiber web server is the problem.

Code snippet

Please take a look at
https://github.com/suntong/fiber_demo/blob/master/app/lru_demo.go

also included below:

package main

import (
    "fmt"
    "log"

    "github.com/gofiber/fiber"
    "github.com/hashicorp/golang-lru/simplelru"
)

func main() {
    app := fiber.New()

    evictCounter := 0
    onEvicted := func(k interface{}, v interface{}) {
        evictCounter++
    }
    // https://godoc.org/github.com/hashicorp/golang-lru/simplelru
    l, err := simplelru.NewLRU(12, onEvicted)
    if err != nil {
        log.Fatalf("err: %v", err)
    }

    // http://localhost:3000/q?p=something
    app.Get("/q", func(c *fiber.Ctx) {
        l.Add(c.Query("p"), nil)
        r := fmt.Sprintln(l.Keys())
        c.Send(r)
    })

    app.Listen(3000)
}
$ go run lru_demo.go 
        _______ __                 
  ____ / ____(_) /_  ___  _____   HOST     0.0.0.0  OS      LINUX
_____ / /_  / / __ \/ _ \/ ___/   PORT     3000     THREADS 12
  __ / __/ / / /_/ /  __/ /       TLS      FALSE    PREFORK FALSE
    /_/   /_/_.___/\___/_/1.14.4  HANDLERS 1        PID     35311

Here is what I got on my first run (duplicated the problem on first attempt that I wanted to duplicate with the LRU algorithm alone for days):

$ curl http://localhost:3000/q?p=AAAAAAAAAAAAA
[AAAAAAAAAAAAA]

$ curl http://localhost:3000/q?p=BBBBBBBBBBBBB
[AAAAAAAAAAAAA BBBBBBBBBBBBB]

$ curl http://localhost:3000/q?p=CCCCCCCCCCCCC
[CCCCCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCCCCC]

$ curl http://localhost:3000/q?p=DDDDDDDDDDDDD
[CCCCCCCCCCCCC BBBBBBBBBBBBB CCCCCCCCCCCCC DDDDDDDDDDDDD]

$ curl http://localhost:3000/q?p=EEEEEEEEEEEEE
[CCCCCCCCCCCCC EEEEEEEEEEEEE CCCCCCCCCCCCC DDDDDDDDDDDDD EEEEEEEEEEEEE]

$ curl http://localhost:3000/q?p=CCCCCCCCCCCCC
[CCCCCCCCCCCCC CCCCCCCCCCCCC DDDDDDDDDDDDD CCCCCCCCCCCCC CCCCCCCCCCCCC]

What's being printed is the LRU entries, which should has been [AA..., BB..., CC..., DD..., EE...] up to the second last step, and the last step should just shuffle the priorities / listing order.

I've been spending days and days on duplicating the problem with the LRU algorithm alone, covering a huge amount of test cases, but all are just fine. I'm not going to list them all here, but this is the most convincing one for me to believe that the LRU algorithm alone is not the problem:

https://play.golang.org/p/5rEGKtiuiO1

Please double check. Might the fiber being multi-threaded be the root cause?
Thanks a lot!

鈽笍 Bug

Most helpful comment

Hi @suntong, this is expected because of the returned value from c.Query() is only valid within the handler.

// Query returns the query string parameter in the url.
// Defaults to empty string "" if the query doesn't exist.
// If a default value is given, it will return that value if the query doesn't exist.
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting to use the value outside the Handler.
func (ctx *Ctx) Query(key string, defaultValue ...string) string

_Returned value is only valid within the handler. Do not store any references.
Make copies or use the Immutable setting to use the value outside the Handler._

    app.Get("/q", func(c *fiber.Ctx) {
        p := utils.ImmutableString(c.Query("p"))
        l.Add(p, nil)
        c.Send(l.Keys())
    })

Read more about Fiber's zero allocation model here https://docs.gofiber.io/#zero-allocation

All 2 comments

Hi @suntong, this is expected because of the returned value from c.Query() is only valid within the handler.

// Query returns the query string parameter in the url.
// Defaults to empty string "" if the query doesn't exist.
// If a default value is given, it will return that value if the query doesn't exist.
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting to use the value outside the Handler.
func (ctx *Ctx) Query(key string, defaultValue ...string) string

_Returned value is only valid within the handler. Do not store any references.
Make copies or use the Immutable setting to use the value outside the Handler._

    app.Get("/q", func(c *fiber.Ctx) {
        p := utils.ImmutableString(c.Query("p"))
        l.Add(p, nil)
        c.Send(l.Keys())
    })

Read more about Fiber's zero allocation model here https://docs.gofiber.io/#zero-allocation

Aha!!! No wonder!

Thanks a lot for the fast response!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MohamedGouaouri picture MohamedGouaouri  路  3Comments

lucasmdomingues picture lucasmdomingues  路  3Comments

faultable picture faultable  路  3Comments

Badrouu17 picture Badrouu17  路  4Comments

koddr picture koddr  路  4Comments