aichaos / rivescript-go

A RiveScript interpreter for Go. RiveScript is a scripting language for chatterbots.
https://www.rivescript.com/
MIT License
60 stars 16 forks source link

Implement pluggable session store and make it threadsafe #12

Closed kirsle closed 7 years ago

kirsle commented 7 years ago

This implements the pluggable session storage feature, similar to RiveScript-Python PR #32.

The new package rivescript-go/sessions defines the SessionManager interface for managing user variables, and the package rivescript-go/sessions/memory implements the default in-memory manager.

:warning: Incompatible Changes :warning:

This changes some of the API functionality. After merging, the library version will be incremented to v0.1.0 accordingly.

The rivescript.New() Constructor

The constructor for rivescript.New() has been updated to accept a config object. Example:

import (
    "github.com/aichaos/rivescript-go"
    "github.com/aichaos/rivescript-go/config"
    "github.com/aichaos/rivescript-go/sessions/memory"
)

func main() {
    // If you want to specify your own config. The zero options
    // have sensible defaults for the most part, so you don't need
    // to specify every key.
    bot := rivescript.New(&config.Config{
        Debug: false, // default
        UTF8: true, // default false
        Strict: true, // default false
        Depth: 50, // default 50 if not defined or <= 0
        SessionManager: memory.New(), // default if not defined
    }

    // For convenience, there are two shortcuts
    bot := rivescript.New(config.Basic())
    bot := rivescript.New(config.UTF8())

    // Or if you don't care you can have all the defaults (this disables strict mode!)
    bot := rivescript.New(nil)
}

The "basic" config profile has Strict=true and all the other defaults, and the UTF8 profile has that, plus UTF8=true.

Other Functions

Thread Safety

This PR will fix #10 because the default MemoryStore implementation of the SessionManager uses a mutex to lock concurrent access to the underlying user data maps.

Additionally, a common mutex rs.cLock protects map access to global variables, bot variables, substitutions, macro handlers and subroutines when using the API methods to modify these. TODO is to use this mutex when a !Definition command is parsed or a <bot> or <env> tag is handled, but these are edge cases and not a high priority right now.

The following Go script verified that the maps are thread safe (this code gave a concurrent read error before implementing this change):

package main

import (
    "fmt"
    "sync"
    "time"

    rivescript "github.com/aichaos/rivescript-go"
    "github.com/aichaos/rivescript-go/config"
)

var done int
var lock sync.Mutex

func main() {
    bot := rivescript.New(config.Basic())
    bot.LoadDirectory("eg/brain")
    bot.Stream(`
        ! var test = hello
        ! global test = hello

        + update test
        - <bot test=world><env test=hello>OK: <bot test>
    `)
    bot.SortReplies()

    for i := 0; i < 10; i++ {
        go ConcurrentTest(fmt.Sprintf("user%d", i), bot)
    }

    for done < 10 {
        time.Sleep(1)
    }
}

func ConcurrentTest(username string, bot *rivescript.RiveScript) {
    fmt.Println("Concurrent test")
    for i := 0; i < 100; i++ {
        _ = bot.Reply(username, "hello bot")
        _ = bot.Reply(username, "update test")
    }
    fmt.Println("Done")

    lock.Lock()
    done++
    lock.Unlock()
}