├── .github └── workflows │ ├── build.sh │ └── main.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── examples └── database.go ├── pmem ├── README.md └── pmem.go ├── pmemtest └── namedFuncs_test.go ├── transaction ├── AUTHORS ├── README.md ├── bitmap.go ├── movnt.go ├── movnt.s ├── optEnd.go ├── redoTx.go ├── transaction.go └── undoTx.go └── txtest ├── crashtest └── txCrash_test.go ├── log3SwizzleTest └── txLog3Swizzle_test.go ├── txExec_test.go ├── txLock_test.go ├── txLog3_test.go ├── txRedoLog_test.go ├── txTestsPmemInit.go └── txUndoLog_test.go /.github/workflows/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Build the modified Go compiler. 6 | git clone --depth 1 https://github.com/jerrinsg/go-pmem ~/go-pmem 7 | cd ~/go-pmem/src 8 | ./make.bash 9 | 10 | cd $GOPATH/src/github.com/vmware/go-pmem-transaction/txtest 11 | 12 | # Force travis CI to use the compiler and toolchain we just built. 13 | # If the test is run as ~/go-pmem/bin/go test, travis is using the tools found 14 | # in /home/travis/.gimme/versions/go1.11.1.linux.amd64 to do the build (TODO). 15 | GOROOT="$HOME/go-pmem/" GOTOOLDIR="$HOME/go-pmem/pkg/tool/linux_amd64" ~/go-pmem/bin/go test 16 | 17 | cd $GOPATH/src/github.com/vmware/go-pmem-transaction/txtest/crashtest 18 | GOROOT="$HOME/go-pmem/" GOTOOLDIR="$HOME/go-pmem/pkg/tool/linux_amd64" ~/go-pmem/bin/go test -tags="crash" 19 | GOROOT="$HOME/go-pmem/" GOTOOLDIR="$HOME/go-pmem/pkg/tool/linux_amd64" ~/go-pmem/bin/go test -tags="crash" 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | env: 18 | GOPATH: ${{ github.workspace }} 19 | 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | 26 | # Runs a single command using the runners shell 27 | - name: Checkout code 28 | uses: actions/checkout@v2 29 | with: 30 | path: ./src/github.com/${{ github.repository }} 31 | - name: Test 32 | run: ./src/github.com/${{ github.repository }}/.github/workflows/build.sh 33 | 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Libraries 2 | *.a 3 | 4 | # Vim temporary files 5 | *.swp 6 | 7 | # Emacs temporary files 8 | *~ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Contributing to go-pmem-transaction 4 | 5 | The go-pmem-transaction project team welcomes contributions from the community. Before you start working with go-pmem-transaction, please read our [Developer Certificate of Origin](https://cla.vmware.com/dco). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. 6 | 7 | ## Contribution Flow 8 | 9 | This is a rough outline of what a contributor's workflow looks like: 10 | 11 | - Create a topic branch from where you want to base your work 12 | - Make commits of logical units 13 | - Make sure your commit messages are in the proper format (see below) 14 | - Push your changes to a topic branch in your fork of the repository 15 | - Submit a pull request 16 | 17 | Example: 18 | 19 | ``` shell 20 | git remote add upstream https://github.com/vmware/go-pmem-transaction.git 21 | git checkout -b my-new-feature master 22 | git commit -a 23 | git push origin my-new-feature 24 | ``` 25 | 26 | ### Staying In Sync With Upstream 27 | 28 | When your branch gets out of sync with the vmware/master branch, use the following to update: 29 | 30 | ``` shell 31 | git checkout my-new-feature 32 | git fetch -a 33 | git pull --rebase upstream master 34 | git push --force-with-lease origin my-new-feature 35 | ``` 36 | 37 | ### Updating pull requests 38 | 39 | If your PR fails to pass CI or needs changes based on code review, you'll most likely want to squash these changes into 40 | existing commits. 41 | 42 | If your pull request contains a single commit or your changes are related to the most recent commit, you can simply 43 | amend the commit. 44 | 45 | ``` shell 46 | git add . 47 | git commit --amend 48 | git push --force-with-lease origin my-new-feature 49 | ``` 50 | 51 | If you need to squash changes into an earlier commit, you can use: 52 | 53 | ``` shell 54 | git add . 55 | git commit --fixup 56 | git rebase -i --autosquash master 57 | git push --force-with-lease origin my-new-feature 58 | ``` 59 | 60 | Be sure to add a comment to the PR indicating your new changes are ready to review, as GitHub does not generate a 61 | notification when you git push. 62 | 63 | ### Formatting Commit Messages 64 | 65 | We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). 66 | 67 | Be sure to include any related GitHub issue references in the commit message. See 68 | [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues 69 | and commits. 70 | 71 | ## Reporting Bugs and Creating Issues 72 | 73 | When opening a new issue, try to roughly follow the commit message format conventions above. 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Persistent Memory and Transactions for Go 2 | 3 | Copyright (c) 2019 VMware, Inc. All rights reserved 4 | 5 | The BSD 3-Clause "New" or "Revised" license (the License) set forth below applies to all parts of the Persistent Memory and Transactions for Go 6 | project. You may not use this file except in compliance with the License.† 7 | 8 | BSD-3 License 9 | 10 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 19 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Persistent Memory and Transactions for Go 2 | 3 | Copyright (c) 2019 VMware, Inc. All Rights Reserved. 4 | 5 | This product is licensed to you under the BSD 3-Clause "New" or "Revised" license (the "License"). You may not use this product except in compliance with the BSD-3 License. 6 | 7 | This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # go-pmem-transaction 4 | 5 | ## Warning 6 | VMware has ended active development of this project, this repository will no longer be updated. 7 | 8 | ## Overview 9 | A library to make persistent memory accessible to developers of Go language. More details about Persistent memory can be found [here](https://docs.pmem.io/). This repostiory has two Go packages (pmem & transaction). 10 | 11 | ### Prerequisites 12 | To use these packages, you need new extensions to the Go language. These changes are maintained in a separate repository [here]( 13 | https://github.com/jerrinsg/go-pmem). 14 | 15 | ### Build & Run 16 | 17 | 1. Download Go source code by cloning the [modified Go source code](https://github.com/jerrinsg/go-pmem). 18 | 2. Build the Go distribution by running (For linux): 19 | ``` 20 | $ cd src 21 | $ ./all.bash 22 | ``` 23 | You can also follow the general instructions for building Go from its source code found [here](https://golang.org/doc/install/source#install). 24 | 25 | 3. `go get -u github.com/vmware/go-pmem-transaction/...` 26 | 4. Make sure to build these packages (and applications using these packages) using the Go binary built in step 2. 27 | 28 | ## Documentation 29 | This repository provides two packages: 30 | 1. *pmem* package that provides access to persistent memory. The pmem package allows users to create data structures that reside in persistent memory and get pointers to these data structures that reside in persistent memory as well. 31 | 32 | 2. The *transaction* package provides undo and redo transaction logging APIs to allow for crash-consistent updates. 33 | 34 | Individual READMEs for these packages can be found in the package directories or here: 35 | 1. [pmem README](https://github.com/vmware/go-pmem-transaction/blob/master/pmem/README.md) 36 | 2. [transaction README](https://github.com/vmware/go-pmem-transaction/blob/master/transaction/README.md) 37 | 38 | ## Contributing 39 | 40 | The go-pmem-transaction project team welcomes contributions from the community. Before you start working with go-pmem-transaction, please read our [Developer Certificate of Origin](https://cla.vmware.com/dco). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. For more detailed information, refer to [CONTRIBUTING.md](CONTRIBUTING.md). 41 | 42 | ## License 43 | go-pmem-transaction is availabe under BSD-3 license. 44 | -------------------------------------------------------------------------------- /examples/database.go: -------------------------------------------------------------------------------- 1 | // A simple linked list application that shows the usage of the pmem and 2 | // transaction package. On the first invocation, it creates a named object 3 | // called dbRoot which holds pointers to the first and last element in the 4 | // linked list. On each run, a new node is added to the linked list and all 5 | // contents of the list are printed. 6 | 7 | package main 8 | 9 | import ( 10 | "math/rand" 11 | "time" 12 | 13 | "github.com/vmware/go-pmem-transaction/pmem" 14 | "github.com/vmware/go-pmem-transaction/transaction" 15 | ) 16 | 17 | const ( 18 | // A magic number used to identify if the root object initialization 19 | // completed successfully. 20 | magic = 0x1B2E8BFF7BFBD154 21 | ) 22 | 23 | // Structure of each node in the linked list 24 | type entry struct { 25 | id int 26 | data []byte 27 | next *entry 28 | } 29 | 30 | // The root object that stores pointers to the elements in the linked list 31 | type root struct { 32 | magic int 33 | head *entry 34 | tail *entry 35 | } 36 | 37 | // Function to generate a random byte slice in persistent memory of length n 38 | func randString(n int) []byte { 39 | b := pmake([]byte, n) // transaction here 40 | tx := transaction.NewUndoTx() 41 | tx.Begin() 42 | tx.Log(b) 43 | for i := range b { 44 | b[i] = byte(rand.Intn(26) + 65) 45 | } 46 | tx.End() 47 | transaction.Release(tx) 48 | return b 49 | } 50 | 51 | // A function that populates the contents of the root object transactionally 52 | func populateRoot(rptr *root) { 53 | tx := transaction.NewUndoTx() 54 | tx.Begin() 55 | tx.Log(rptr) 56 | rptr.magic = magic 57 | rptr.head = nil 58 | rptr.tail = nil 59 | tx.End() 60 | transaction.Release(tx) 61 | } 62 | 63 | // Adds a node to the linked list and updates the tail (and head if empty) 64 | // All data updates are handled transactionally 65 | func addNode(rptr *root) { 66 | entry := pnew(entry) 67 | tx := transaction.NewUndoTx() 68 | tx.Begin() 69 | tx.Log(entry) 70 | tx.Log(rptr) 71 | entry.id = rand.Intn(100) 72 | entry.data = randString(10) 73 | 74 | if rptr.head == nil { 75 | rptr.head = entry 76 | } else { 77 | tx.Log(&rptr.tail.next) 78 | rptr.tail.next = entry 79 | } 80 | rptr.tail = entry 81 | 82 | tx.End() 83 | transaction.Release(tx) 84 | 85 | } 86 | 87 | // Print all the nodes currently in the linked list 88 | func printNodes(rptr *root) { 89 | entry := rptr.head 90 | for entry != nil { 91 | println("id = ", entry.id, " data = ", string(entry.data)) 92 | entry = entry.next 93 | } 94 | } 95 | 96 | func main() { 97 | rand.Seed(time.Now().UTC().UnixNano()) 98 | firstInit := pmem.Init("/mnt/ext4-pmem0/database") 99 | var rptr *root 100 | if firstInit { 101 | // Create a new named object called dbRoot and point it to rptr 102 | rptr = (*root)(pmem.New("dbRoot", rptr)) 103 | populateRoot(rptr) 104 | } else { 105 | // Retrieve the named object dbRoot 106 | rptr = (*root)(pmem.Get("dbRoot", rptr)) 107 | if rptr.magic != magic { 108 | // An object named dbRoot exists, but its initialization did not 109 | // complete previously. 110 | populateRoot(rptr) 111 | } 112 | } 113 | addNode(rptr) // Add a new node in the linked list 114 | printNodes(rptr) // Print out the contents of the linked list 115 | } 116 | -------------------------------------------------------------------------------- /pmem/README.md: -------------------------------------------------------------------------------- 1 | This document explains the functions and concepts of `pmem` package which 2 | provides a way for Go applications to access persistent memory. It 3 | has been written to work in conjunction with the Go trasaction 4 | [package](https://github.com/vmware/go-pmem-transaction/tree/master/transaction) 5 | and changes made to Go compiler to support persistent memory in a separate 6 | [project](https://github.com/jerrinsg/go-pmem). 7 | 8 | Any data in volatile memory is lost on restart, so volatile pointers pointing to 9 | data in non-volatile memory (V-to-NV pointers) will be lost too. This leaves 10 | NV-to-NV pointers as the only way for applications to access pmem across 11 | restarts. We allow the applications to retrieve some of these NV-to-NV pointers 12 | through string names. These can then be used to navigate other objects stored in 13 | pmem. We call these objects “named objects” and they can be pointers/Go slices. 14 | 15 | The following functions are accessible to the users of this package: 16 | 17 | 1. `Init(fileName string) bool` 18 | Expects a filename as its only argument. This is the name of the persistent 19 | memory file that the application will be using. This function takes care of 20 | detecting if the application crashed in the past, and if there are any 21 | incomplete updates stored in the transaction logs. Based on whether these 22 | updates were committed or not, the updates are applied/dropped respectively. 23 | This function internally calls `transaction.Init()` of transaction 24 | [package](https://github.com/vmware/go-pmem-transaction/tree/master/transaction) 25 | Example: 26 | ```go 27 | if pmem.Init("myTestFile") { 28 | // true meaning first time application run 29 | } else { 30 | // myTestFile already exists, and this is application restart 31 | } 32 | ``` 33 | 34 | 2. `New(name string, intf interface{}) unsafe.Pointer` 35 | This function is similar to using Go's new() function. It allocates space for a 36 | data structure and returns pointer to it. The data structure is passed as the 37 | 2nd argument, and the pointer is returned as an unsafe pointer to allow any user 38 | data structure to be created. In addition, it provides a way to create a name to 39 | NV pointer mapping. Using this name, the application can retreive a NV-pointer 40 | in subsequent restarts. 41 | If the application tries to create an object with a name that already exists, 42 | this function panics. Example use: 43 | ```go 44 | var a *int 45 | a = (*int)(pmem.New("region1", a)) 46 | *a = 10 //Space for a allocated inside New() 47 | var st1 *myStruct 48 | st1 = (*myStruct)(pmem.New("myObject", st1)) 49 | ``` 50 | If the 2nd argument is a slice, the function crashes. 51 | 52 | 3. `func Get(name string, intf interface{}) unsafe.Pointer` 53 | Data structures created using `New()` can be retrieved using this function. If 54 | the returned value is nil, no data structure with the given name exists. If a 55 | data structure with the given name exists, but is of different type than the 56 | 2nd argument, the function crashes. If the 2nd argument is a slice, the function 57 | crashes. Example use: 58 | ```go 59 | var b *int 60 | b = (*int)(pmem.Get("region1", b)) // region1 created before in example of New() 61 | if b == nil { 62 | // region1 doesn't exist 63 | } else { 64 | // region1 exists, b = 10. Continuing from example of New() 65 | } 66 | ``` 67 | 68 | 4. `Make(name string, intf ...interface{}) interface{}` 69 | Similar to New(), except this allows applications to create slices with names, 70 | or a name-to-nonvolatile slice mapping. This function is similar to Go's make(). 71 | It crashes if the 2nd argument is not a slice. The length of slice is passed as 72 | the 3rd argument. Similar to New(), this function panics if the application 73 | tries to create an object with a name that already exists. Example: 74 | ```go 75 | var slice1 []float64 76 | slice1 = pmem.Make("region2", slice1, 10).([]float64) // len(slice1) = 10 77 | slice1[0] = 1.1 78 | ``` 79 | 80 | 5. `GetSlice(name string, intf ...interface{}) interface{}` 81 | Similar to `Get()`, except this allows applications to retrieve slices created 82 | using `Make()`. This function returns a nil interface if no slice with the given 83 | name exists. If a slice with the given name exists, but is of different type 84 | than the 2nd argument, the function crashes. If the 2nd argument is not a slice, 85 | the function crashes. Example use: 86 | ```go 87 | var slice2 []float64 88 | slice2 = pmem.GetSlice("region2", slice2).([]float64) // Get same slice 89 | // slice2[0] = 1.1 ... continuing from example for Make() 90 | ``` 91 | 92 | 6. `Delete(name string) error` 93 | Allows applications to delete a named object. The name can be reused to create 94 | a new data structure or slice. References to the existing object are removed 95 | internally, so the non-volatile data structure/slice can be garbage collected. 96 | Returns an error if no object with the name existed before. -------------------------------------------------------------------------------- /pmem/pmem.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | package pmem 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "log" 12 | "reflect" 13 | "runtime" 14 | "sync" 15 | "unsafe" 16 | 17 | "github.com/vmware/go-pmem-transaction/transaction" 18 | ) 19 | 20 | type ( 21 | namedObject struct { 22 | name []byte 23 | typ []byte 24 | ptr unsafe.Pointer 25 | } 26 | pmemHeader struct { 27 | // Transaction Log Header. We don't know what the app might use. So, 28 | // initialize both undo & redo log 29 | undoTxHeadPtr unsafe.Pointer 30 | redoTxHeadPtr unsafe.Pointer 31 | 32 | // App-specific data structures, updated on named New, Make calls 33 | // TODO: Use map, since this is a key-value pair, but Persistent maps 34 | // are not supported yet. 35 | appData []namedObject 36 | } 37 | ) 38 | 39 | var ( 40 | rootPtr *pmemHeader 41 | m *sync.RWMutex // Updates to map are thread safe through this lock 42 | ) 43 | 44 | const ( 45 | sliceHeaderSize = 24 // size of a slice header 46 | ) 47 | 48 | func populateTxHeaderInRoot() { 49 | rootPtr.undoTxHeadPtr = transaction.Init(rootPtr.undoTxHeadPtr, "undo") 50 | rootPtr.redoTxHeadPtr = transaction.Init(rootPtr.redoTxHeadPtr, "redo") 51 | } 52 | 53 | // Init returns true if this was a first time initialization. 54 | func Init(fileName string) bool { 55 | // Register application callback function 56 | // This function is called during heap recovery before pointers are swizzled 57 | runtime.AppCallBack = transaction.SwizzleAndAbort 58 | 59 | runtimeRootPtr, err := runtime.PmemInit(fileName) 60 | if err != nil { 61 | log.Fatal("Persistent memory initialization failed") 62 | } 63 | var firstInit bool 64 | if runtimeRootPtr == nil { // first time initialization 65 | rootPtr = pnew(pmemHeader) 66 | populateTxHeaderInRoot() 67 | rootPtr.appData = pmake([]namedObject, 1) // Start with size of 1 68 | runtime.PersistRange(unsafe.Pointer(rootPtr), 69 | unsafe.Sizeof(*rootPtr)) 70 | runtime.SetRoot(unsafe.Pointer(rootPtr)) 71 | firstInit = true 72 | } else { 73 | rootPtr = (*pmemHeader)(runtimeRootPtr) 74 | populateTxHeaderInRoot() 75 | } 76 | m = new(sync.RWMutex) 77 | return firstInit 78 | } 79 | 80 | type value struct { 81 | typ unsafe.Pointer 82 | ptr unsafe.Pointer 83 | flag uintptr 84 | } 85 | 86 | // sliceHeader is the datastructure representation of a slice object 87 | type sliceHeader struct { 88 | data unsafe.Pointer 89 | len int 90 | cap int 91 | } 92 | 93 | // Make returns the interface to the slice asked for, slice being created in 94 | // persistent heap. Only supports slices for now. If an object with same name 95 | // already exists, it panics. 96 | // Syntax: var s []int 97 | // s = pmem.Make("myName", s, 10).([]int) 98 | func Make(name string, intf ...interface{}) interface{} { 99 | v1 := reflect.ValueOf(intf[0]) 100 | if v1.Kind() != reflect.Slice { 101 | log.Fatal("Can only pmem.Make slice") 102 | } 103 | m.RLock() 104 | found, _ := exists(name) 105 | m.RUnlock() 106 | if found { 107 | panic(fmt.Sprintf("Object %s already exists", name)) 108 | } 109 | 110 | sTyp := v1.Type() 111 | sTypString := sTyp.PkgPath() + sTyp.String() 112 | v2 := reflect.ValueOf(intf[1]) 113 | sLen := int(v2.Int()) 114 | newV := reflect.PMakeSlice(sTyp, sLen, sLen) 115 | vPtr := (*value)(unsafe.Pointer(&newV)) 116 | sliceHdr := pnew(sliceHeader) 117 | *sliceHdr = *(*sliceHeader)(vPtr.ptr) 118 | runtime.PersistRange(unsafe.Pointer(sliceHdr), unsafe.Sizeof(*sliceHdr)) 119 | nameByte := pmake([]byte, len(name)) 120 | sTypByte := pmake([]byte, len(sTypString)) 121 | copy(nameByte, name) 122 | copy(sTypByte, sTypString) 123 | runtime.PersistRange(unsafe.Pointer(&nameByte[0]), uintptr(len(nameByte))) 124 | runtime.PersistRange(unsafe.Pointer(&sTypByte[0]), uintptr(len(sTypByte))) 125 | newNamedObj := namedObject{nameByte, sTypByte, unsafe.Pointer(sliceHdr)} 126 | tx := transaction.NewUndoTx() 127 | m.Lock() 128 | tx.Begin() 129 | tx.Log3(unsafe.Pointer(&rootPtr.appData), sliceHeaderSize) // add to root pointer 130 | rootPtr.appData = append(rootPtr.appData, newNamedObj) 131 | tx.End() 132 | m.Unlock() 133 | transaction.Release(tx) 134 | slicePtrWithTyp := reflect.NewAt(sTyp, unsafe.Pointer(sliceHdr)) 135 | sliceVal := reflect.Indirect(slicePtrWithTyp) 136 | return sliceVal.Interface() 137 | } 138 | 139 | // New is used to create named objects in persistent heap. This object would 140 | // survive crashes. Returns unsafe.Pointer to the object if the creation was 141 | // successful. If an object with same name already exists, it panics. 142 | // Syntax: var a *int 143 | // a = (*int)(pmem.New("myName", a)) 144 | func New(name string, intf interface{}) unsafe.Pointer { 145 | v := reflect.ValueOf(intf) 146 | if v.Kind() == reflect.Slice { 147 | log.Fatal("Cannot create new slice with New. Try Make") 148 | } 149 | t := v.Type() 150 | ts := t.PkgPath() + t.String() 151 | m.RLock() 152 | found, _ := exists(name) 153 | m.RUnlock() 154 | if found { 155 | panic(fmt.Sprintf("Object %s already exists", name)) 156 | } 157 | nameByte := pmake([]byte, len(name)) 158 | tByte := pmake([]byte, len(ts)) 159 | newObj := reflect.PNew(t.Elem()) //Elem() returns type of object t points to 160 | copy(nameByte, name) 161 | copy(tByte, ts) 162 | runtime.PersistRange(unsafe.Pointer(&nameByte[0]), uintptr(len(nameByte))) 163 | runtime.PersistRange(unsafe.Pointer(&tByte[0]), uintptr(len(tByte))) 164 | newNamedObj := namedObject{nameByte, tByte, unsafe.Pointer(newObj.Pointer())} 165 | tx := transaction.NewUndoTx() 166 | m.Lock() 167 | tx.Begin() 168 | tx.Log3(unsafe.Pointer(&rootPtr.appData), sliceHeaderSize) // add to root pointer 169 | rootPtr.appData = append(rootPtr.appData, newNamedObj) 170 | tx.End() 171 | m.Unlock() 172 | transaction.Release(tx) 173 | return unsafe.Pointer(newObj.Pointer()) 174 | } 175 | 176 | // Delete deletes a named object created using New or Make. Returns error if 177 | // no such object exists 178 | func Delete(name string) error { 179 | m.Lock() 180 | found, i := exists(name) 181 | defer m.Unlock() 182 | if !found { 183 | return errors.New("No such object allocated before") 184 | } 185 | tx := transaction.NewUndoTx() 186 | tx.Begin() 187 | tx.Log3(unsafe.Pointer(&rootPtr.appData), sliceHeaderSize) 188 | rootPtr.appData = append(rootPtr.appData[:i], rootPtr.appData[i+1:]...) 189 | tx.End() 190 | transaction.Release(tx) 191 | return nil 192 | } 193 | 194 | // Get the named object if it exists. Returns an unsafe pointer to the object 195 | // if it was made before. Return nil otherwise. Syntax same as New() 196 | func Get(name string, intf interface{}) unsafe.Pointer { 197 | m.RLock() 198 | found, i := exists(name) 199 | defer m.RUnlock() 200 | if !found { 201 | return nil 202 | } 203 | v := reflect.ValueOf(intf) 204 | if v.Kind() == reflect.Slice { 205 | log.Fatal("Cannot get slice with Get. Try GetSlice") 206 | } 207 | t := v.Type() 208 | ts := t.PkgPath() + t.String() 209 | obj := rootPtr.appData[i] 210 | if string(obj.typ[:]) != ts { 211 | log.Fatal("Object ", string(obj.name[:]), "was created before with ", 212 | "type ", string(obj.typ[:])) 213 | } 214 | return obj.ptr 215 | } 216 | 217 | // GetSlice is Get() for named slices. Syntax same as Make() 218 | func GetSlice(name string, intf ...interface{}) interface{} { 219 | v1 := reflect.ValueOf(intf[0]) 220 | if v1.Kind() != reflect.Slice { 221 | log.Fatal("Can only GetSlice to retrieve named slices") 222 | } 223 | m.RLock() 224 | found, i := exists(name) 225 | defer m.RUnlock() 226 | if !found { 227 | return nil 228 | } 229 | sTyp := v1.Type() 230 | sTypString := sTyp.PkgPath() + sTyp.String() 231 | obj := rootPtr.appData[i] 232 | if string(obj.typ[:]) != sTypString { 233 | log.Fatal("Object ", string(obj.name[:]), " was made before with type ", 234 | string(obj.typ[:])) 235 | } 236 | slicePtrWithTyp := reflect.NewAt(sTyp, obj.ptr) 237 | sliceVal := reflect.Indirect(slicePtrWithTyp) 238 | return sliceVal.Interface() 239 | } 240 | 241 | // The lock protecting appData should be acquired before calling this function 242 | func exists(name string) (found bool, i int) { 243 | var obj namedObject 244 | for i, obj = range rootPtr.appData { 245 | if string(obj.name[:]) == name { 246 | found = true 247 | break 248 | } 249 | } 250 | return 251 | } 252 | -------------------------------------------------------------------------------- /pmemtest/namedFuncs_test.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | package pmemtest 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "os" 12 | "os/exec" 13 | "runtime/debug" 14 | "testing" 15 | "time" 16 | 17 | "github.com/vmware/go-pmem-transaction/pmem" 18 | "github.com/vmware/go-pmem-transaction/transaction" 19 | ) 20 | 21 | type structPmemTest struct { 22 | a int 23 | sptr *structPmemTest 24 | slice []int 25 | b bool 26 | } 27 | 28 | func init() { 29 | pmem.Init("tx_testFile") 30 | } 31 | 32 | func TestAPIs(t *testing.T) { 33 | tx := transaction.NewUndoTx() 34 | // named New with int type 35 | // cannot call pmem.New("region1", int) as this pmem package is outside 36 | // compiler 37 | fmt.Println("Testing New int region1") 38 | var a *int 39 | a = (*int)(pmem.New("region1", a)) 40 | tx.Begin() 41 | tx.Log(a) 42 | *a = 10 43 | tx.End() // since update is within log, no need to call persistRange 44 | assertEqual(t, *a, 10) 45 | var b *int 46 | b = (*int)(pmem.Get("region1", b)) 47 | assertEqual(t, *b, 10) 48 | 49 | // named Make for slice of integers 50 | fmt.Println("Testing Make []float64 region2") 51 | var slice1 []float64 52 | slice1 = pmem.Make("region2", slice1, 10).([]float64) 53 | tx.Begin() 54 | tx.Log(slice1) 55 | slice1[0] = 1.1 56 | tx.End() 57 | var slice2 []float64 58 | slice2 = pmem.GetSlice("region2", slice2, 10).([]float64) // Get same slice 59 | assertEqual(t, slice2[0], 1.1) 60 | assertEqual(t, len(slice2), 10) 61 | 62 | // named New with struct type 63 | fmt.Println("Testing New struct region3") 64 | var st1 *structPmemTest 65 | st1 = (*structPmemTest)(pmem.New("region3", st1)) 66 | tx.Begin() 67 | tx.Log(st1) 68 | st1.a = 20 69 | st1.b = true 70 | st1.slice = pmake([]int, 100) 71 | tx.Log(st1.slice) 72 | st1.slice[0] = 11 73 | st1.slice[99] = 22 74 | tx.End() 75 | var st2 *structPmemTest 76 | st2 = (*structPmemTest)(pmem.Get("region3", st2)) // Get back same struct 77 | assertEqual(t, st2.a, 20) 78 | assertEqual(t, st2.b, true) 79 | assertEqual(t, len(st2.slice), 100) 80 | assertEqual(t, st2.slice[0], 11) 81 | assertEqual(t, st2.slice[99], 22) 82 | 83 | fmt.Println("Testing Delete with int & region4") 84 | var c *int 85 | c = (*int)(pmem.New("region4", c)) 86 | tx.Begin() 87 | tx.Log(c) 88 | *c = 100 89 | tx.End() 90 | if err := pmem.Delete("region4"); err != nil { 91 | assert(t) 92 | } 93 | err1 := pmem.Delete("region4") // This should return error 94 | assertEqual(t, err1.Error(), errors.New("No such object allocated before"). 95 | Error()) 96 | c = (*int)(pmem.New("region4", c)) 97 | assertEqual(t, *c, 0) // above New() call would have allocated a new int 98 | pmem.Delete("region4") 99 | 100 | fmt.Println("Testing Update") 101 | var st3 *structPmemTest 102 | st3 = (*structPmemTest)(pmem.New("region4", st3)) 103 | tx.Begin() 104 | tx.Log(st3) 105 | *st3 = *st2 106 | tx.End() 107 | assertEqual(t, st2.a, st3.a) 108 | assertEqual(t, st2.b, st3.b) 109 | assertEqual(t, st2.sptr, st3.sptr) 110 | assertEqual(t, len(st2.slice), len(st3.slice)) 111 | assertEqual(t, st2.slice[0], st3.slice[0]) 112 | assertEqual(t, st2.slice[99], st3.slice[99]) 113 | var d *int 114 | if pmem.Get("region4", st3) != nil { 115 | if err := pmem.Delete("region4"); err != nil { 116 | assert(t) 117 | } 118 | 119 | d = (*int)(pmem.New("region4", d)) 120 | *d = 1 121 | assertEqual(t, *d, 1) 122 | } else { 123 | assert(t) 124 | } 125 | if pmem.Get("region4", d) == nil { 126 | assert(t) 127 | } 128 | 129 | transaction.Release(tx) 130 | fmt.Println("Going to sleep for 10s. Crash here & run TestCrashRetention" + 131 | " to test crash consistency") 132 | time.Sleep(10 * time.Second) 133 | } 134 | 135 | func TestCrashRetention(t *testing.T) { 136 | fmt.Println("Getting region1 int") 137 | var a *int 138 | a = (*int)(pmem.Get("region1", a)) 139 | assertEqual(t, *a, 10) 140 | 141 | fmt.Println("Getting region2 []float64") 142 | var s []float64 143 | s = pmem.GetSlice("region2", s, 10).([]float64) 144 | assertEqual(t, s[0], 1.1) 145 | assertEqual(t, len(s), 10) 146 | 147 | fmt.Println("Getting region3 struct") 148 | var st *structPmemTest 149 | st = (*structPmemTest)(pmem.Get("region3", st)) 150 | assertEqual(t, st.a, 20) 151 | assertEqual(t, st.b, true) 152 | assertEqual(t, len(st.slice), 100) 153 | assertEqual(t, st.slice[0], 11) 154 | assertEqual(t, st.slice[99], 22) 155 | 156 | fmt.Println("Getting region4 which was updated to int") 157 | var b *int 158 | b = (*int)(pmem.Get("region4", b)) 159 | assertEqual(t, *b, 1) 160 | } 161 | 162 | func apiCrash1() { 163 | var a *int 164 | a = (*int)(pmem.New("region5", a)) 165 | var b *float64 166 | b = (*float64)(pmem.Get("region5", b)) 167 | } 168 | 169 | func apiCrash2() { 170 | var a []int 171 | a = pmem.Make("region6", a, 10).([]int) 172 | var b []float64 173 | b = pmem.GetSlice("region6", b).([]float64) 174 | } 175 | 176 | func apiCrash3() { 177 | var a *int 178 | a = (pmem.Make("region7", a, 10)).(*int) 179 | } 180 | 181 | func TestAPICrash1(t *testing.T) { 182 | fmt.Println("Testing API crash for New() & Get()") 183 | if os.Getenv("CRASH_RUN") == "1" { 184 | apiCrash1() 185 | return 186 | } 187 | cmd := exec.Command(os.Args[0], "-test.run=TestAPICrash1") 188 | cmd.Env = append(os.Environ(), "CRASH_RUN=1") 189 | err := cmd.Run() 190 | if e, ok := err.(*exec.ExitError); ok && !e.Success() { 191 | if pmem.Delete("region5") != nil { 192 | assert(t) 193 | } 194 | return 195 | } 196 | t.Fatalf("process ran with err %v, want exit status 1", err) 197 | } 198 | 199 | func TestAPICrash2(t *testing.T) { 200 | fmt.Println("Testing API crash for Make() & GetSlice()") 201 | if os.Getenv("CRASH_RUN") == "1" { 202 | apiCrash2() 203 | return 204 | } 205 | cmd := exec.Command(os.Args[0], "-test.run=TestAPICrash2") 206 | cmd.Env = append(os.Environ(), "CRASH_RUN=1") 207 | err := cmd.Run() 208 | if e, ok := err.(*exec.ExitError); ok && !e.Success() { 209 | if pmem.Delete("region6") != nil { 210 | assert(t) 211 | } 212 | return 213 | } 214 | t.Fatalf("process ran with err %v, want exit status 1", err) 215 | } 216 | 217 | func TestAPICrash3(t *testing.T) { 218 | fmt.Println("Testing API crash for incorrect args to Make()") 219 | if os.Getenv("CRASH_RUN") == "1" { 220 | apiCrash3() 221 | return 222 | } 223 | cmd := exec.Command(os.Args[0], "-test.run=TestAPICrash3") 224 | cmd.Env = append(os.Environ(), "CRASH_RUN=1") 225 | err := cmd.Run() 226 | if e, ok := err.(*exec.ExitError); ok && !e.Success() { 227 | if pmem.Delete("region7") == nil { // region 7 should not be created 228 | assert(t) 229 | } 230 | return 231 | } 232 | t.Fatalf("process ran with err %v, want exit status 1", err) 233 | } 234 | 235 | func TestAPICrash4(t *testing.T) { 236 | fmt.Println("Testing API crash for New() call with already existing " + 237 | "object name") 238 | defer func() { 239 | if r := recover(); r != nil { 240 | fmt.Println("New() panicked successfully with msg:", r) 241 | pmem.Delete("region8") 242 | } 243 | }() 244 | var a *int 245 | a = (*int)(pmem.New("region8", a)) 246 | var b *int 247 | b = (*int)(pmem.New("region8", b)) 248 | } 249 | 250 | func TestAPICrash5(t *testing.T) { 251 | fmt.Println("Testing API crash for Make() call with already existing " + 252 | "object name") 253 | defer func() { 254 | if r := recover(); r != nil { 255 | fmt.Println("Make() panicked successfully with msg:", r) 256 | pmem.Delete("region9") 257 | } 258 | }() 259 | var s1 []int 260 | s1 = pmem.Make("region9", s1, 10).([]int) 261 | var s2 []int 262 | s2 = pmem.Make("region9", s2, 10).([]int) 263 | } 264 | 265 | func assert(t *testing.T) { 266 | assertEqual(t, 0, 1) 267 | } 268 | 269 | func assertEqual(t *testing.T, a interface{}, b interface{}) { 270 | if a != b { 271 | debug.PrintStack() 272 | t.Fatal(fmt.Sprintf("%v != %v", a, b)) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /transaction/AUTHORS: -------------------------------------------------------------------------------- 1 | # The initial code for the implementation of transaction package and undo 2 | # transactions was taken from the implementation of undo transactions by 3 | # Yi Yao (yyi@vmware.com) for Go-redis (https://gitlab.eng.vmware.com/afg/pmem) 4 | # during his time with the Application Focus Group (AFG) at VMware. -------------------------------------------------------------------------------- /transaction/README.md: -------------------------------------------------------------------------------- 1 | This document explains the usage and functionality of transaction package. 2 | 3 | The package implements undo logging and redo logging. So, we currently support 4 | undo and redo transactions. Any other kind of transaction can be implemented 5 | given it satisfies the `TX` interface. The package has been implemented to be 6 | used in conjunction with the persistent memory (pmem) changes being made to Go 7 | runtime in a separate [project](https://github.com/jerrinsg/go-pmem). 8 | 9 | The transaction package is initialized through function 10 | `Init(logHeadPtr unsafe.Pointer, logType string)` . If the first argument is 11 | not nil, application already has some data stored in the transaction logs in one 12 | of its previous runs. In this case, recovery is performed to restore consistent 13 | state of stored application data. The 2nd argument specifies whether the user 14 | wants a "undo" log or "redo" log. 15 | Example usage: `transaction.Init(nil, "undo")` 16 | This function is internally called by `Init()` function of `pmem` 17 | [package](https://github.com/vmware/go-pmem-transaction/tree/master/pmem) 18 | and need not be called by applications explicitly. 19 | 20 | The transaction variables can be initialized using package functions 21 | `transaction.NewUndoTx()` or `transaction.NewRedoTx()` 22 | 23 | The `TX` interface requires the following methods to be implemented: 24 | 25 | 1. `Begin() error` 26 | This marks the beginning of a new transaction. Nested transactions are supported 27 | and can be started by calling `Begin()` before the outer transaction completes. 28 | 29 | 2. `End() bool` 30 | This marks the end of the ongoing transaction and is equivalent to committing a 31 | transaction. On the end of a transaction, all the changes known to the log are 32 | persisted to pmem. Note that updates to inner transactions are not persisted if 33 | there is a crash before the outer transaction is complete. For undo transaction, 34 | calling `End()` makes sure all updates to variables logged using `Log()` are 35 | persisted to pmem. If a sliceheader was logged, any changes to the sliceheader 36 | and any changes to the elements of slice are persisted to pmem as well. All the 37 | data stored in the undo log is discarded after everything has been persisted by 38 | setting the `tail` of log to zero. If there was a crash when `End()` was being 39 | executed, a subsequent application restart would call `abort()` which reverts 40 | all the updates made. 41 | 42 | For redo transactions, calling `End()` causes all updates in the redo log to be 43 | persisted to pmem. Once this is done, the transaction is marked committed. All 44 | the updates in the redo log are now transferred to the program variables. If 45 | there is a crash, a subsequent application restart checks the value of 46 | `committed` variable. If marked true, all the updates in the redo log are 47 | transferred to the program variables. Since we support value-based logging, this 48 | operation is idempotent and works across multiple crashes. If `committed` is 49 | false, all updates in redo log are dropped by calling `abort()` 50 | 51 | 3. `Log(...interface{}) error` 52 | For undo transactions, the expected syntax is `Log(ptr)` or `Log(slice)`. If 53 | `Log()` is called with more than one argument for undo transactions, we return 54 | an error immediately. Undo log captures the state of the variable before the 55 | update and allows the update in-place. So, to successfully log a data structure 56 | in undo log, we need the address and the size of the data structure. The size of 57 | the data structure is obtained from Go’s type system inside this function. So, 58 | only one argument needs to be passed. Typical usage for undo tx looks like: 59 | ```go 60 | tx.Begin() 61 | tx.Log(&node) 62 | node.next = newNode 63 | node.val = newVal 64 | tx.Log(&myStruct.i) 65 | myStruct.i = 2 66 | tx.Log(mySlice) 67 | mySlice[2] = 100 68 | tx.End() 69 | ``` 70 | 71 | For redo transactions, the expected syntax is `Log(ptr, new value)` or 72 | `Log (old slice, new slice)`. If `Log()` is called with more/less than two 73 | arguments for redo transactions, or if there is a mismtach in the type of the 74 | old and new value, an error is returned immediately. Redo log creates a new 75 | copy of the variable on the first update and flushes changes to program data 76 | structures only on transaction commit. So, to log a data structure in redo log, 77 | the new value needs to be provided too. Typical usage for redo tx looks like: 78 | 79 | ```go 80 | tx.Begin() 81 | tx.Log(&node.next, newNode) 82 | tx.Log(&node.val, newVal) 83 | tx.Log(&myStruct.i, 2) 84 | newSlice[2] = 100 85 | tx.Log(mySlice, newSlice) 86 | tx.End() 87 | ``` 88 | 89 | 4. `ReadLog(interface{}) interface{}` 90 | Updates in a redo transaction are not made in-place. As such, any Log() call in 91 | a redo transaction creates a new copy of the variable. If the application wants 92 | to read the latest update made within the transaction, reading the variable 93 | gives old data. `ReadLog()` method does this. It expects the pointer to the 94 | variable whose latest value is needed. If the pointer was never logged before 95 | and not stored in the redo log before, the latest value at the memory location 96 | is returned. 97 | 98 | All the updates to variables in an undo logging mechanism are made in-place. 99 | So, the latest updates can be read by directly reading the variable. 100 | So, this method is not supported for undo transactions. Currently, we return an 101 | empty interface if this method is called with undo transactions. 102 | 103 | 5. `RLock(*sync.RWMutex)`/ `WLock(*sync.RWMutex)` / `Lock(*sync.RWMutex)` 104 | Updates within a transaction should not be visible outside until the transaction 105 | is committed. This is the Isolation property of transactions. In our case, users 106 | have two options: 107 | a) Acquire all the locks before the transaction begins, and release all the 108 | locks after the transaction is over. Thus a typical usage pattern would be: 109 | ```go 110 | m1 := new(sync.RWMutex) 111 | m2 := new(sync.RWMutex) 112 | m1.Lock() 113 | m2.Lock() 114 | tx.Begin() 115 | // Application code 116 | // may include a function call 117 | // This should not include acquiring locks and then updating data structures 118 | // through transaction since the update would be visible before tx is committed. 119 | // If there is a crash here, tx would revert this update but it 120 | // has already been seen, causing inconsistency. 121 | tx.End() 122 | m1.Unlock() 123 | m2.Unlock() 124 | ``` 125 | This approach is similar to strict 2-Phase Locking of database transactions. 126 | 127 | b) Acquire locks through `RLock()`/`WLock()`/`Lock()` provided by transactions. 128 | Calling `WLock` or `Lock` acquires the write lock on the RWMutex, wherease 129 | `RLock` acquires the read lock. All these calls also store the address of the 130 | mutex. All the locks acquired within a transaction are released when the 131 | transaction completes successfully or if it is aborted. Using this provides the 132 | application the flexibility to acquire locks just before data strcutures are 133 | accessed. A typical usage pattern would be: 134 | ```go 135 | m1 := new(sync.RWMutex) 136 | m2 := new(sync.RWMutex) 137 | tx.Begin() 138 | tx.Lock(m1) 139 | tx.Lock(m2) 140 | // Application code 141 | tx.End() 142 | ``` 143 | This approach is similar to 2-Phase Locking of database transactions. 144 | 145 | 6. `abort() error` 146 | This method is not accessible to users of the package, but as the name suggests 147 | it would abort an ongoing transaction, reverting all the updates if the 148 | transaction has not been committed. If the transaction is already marked 149 | committed (This path is taken only for redo transactions), all updates are 150 | retried. To trigger `abort()`, users can instead use the package function 151 | `transaction.Release(tx)` 152 | 153 | 7. `Exec(...interface{}) ([]reflect.Value, error)` 154 | This method allows users to call functions that would be executed within a 155 | transaction, and can be particularly useful for closures. Typical use would be: 156 | ```go 157 | func add(tx transaction.TX, a *int, b *int) { 158 | tx.Log(a) 159 | a = a + b 160 | return 161 | } 162 | // code 163 | // code 164 | retVal, err = tx.Exec(add, a, b) 165 | fmt.Println((int)(retVal[0].Int())) 166 | ``` 167 | 168 | OR 169 | 170 | ```go 171 | // code 172 | _, err = tx.Exec(func(tx transaction.TX) { 173 | tx.Log(&a) 174 | a = a + b // a & b are variables outside this closure 175 | }) 176 | ``` 177 | 178 | More usage of transactions can be seen in the **tests/** directory. -------------------------------------------------------------------------------- /transaction/bitmap.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | "sync/atomic" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | bitsPerByte = 8 12 | ) 13 | 14 | type bitmap struct { 15 | bitArray []uint32 16 | // cachedIndex is a hint as to where to begin the next search for an unset bit 17 | cachedIndex int 18 | } 19 | 20 | // Create a new bitmap datastructure with space allocated for the backing array. 21 | func newBitmap(length int) *bitmap { 22 | bitArray := make([]uint32, length) 23 | return &bitmap{bitArray, 0} 24 | } 25 | 26 | // changeBit atomically tries to change the bit at index 'b' from old to new. It 27 | // returns true if this was successful, and false in all other cases. 28 | func (bm *bitmap) changeBit(b int, old, new uint32) bool { 29 | bitAddr := (*uint32)(unsafe.Pointer(&bm.bitArray[b])) 30 | return atomic.CompareAndSwapUint32(bitAddr, old, new) 31 | } 32 | 33 | // Returns the next index in the bitmap that is unset. 34 | func (bm *bitmap) nextAvailable() int { 35 | ciAddr := (*int64)(unsafe.Pointer(&bm.cachedIndex)) 36 | 37 | for { 38 | ind := int(atomic.LoadInt64(ciAddr)) 39 | ln := len(bm.bitArray) 40 | for i := 0; i < ln; i++ { 41 | b := (ind + i) % ln 42 | if bm.changeBit(b, 0, 1) { 43 | return b 44 | } 45 | } 46 | // No unset bit at this time. Let some other goroutine (if available) 47 | // run instead. 48 | runtime.Gosched() 49 | } 50 | } 51 | 52 | // clearBit clears the bit at index 'b'' 53 | func (bm *bitmap) clearBit(b int) { 54 | if !bm.changeBit(b, 1, 0) { 55 | log.Fatal("Bit already unset") 56 | } 57 | ciAddr := (*int64)(unsafe.Pointer(&bm.cachedIndex)) 58 | atomic.StoreInt64(ciAddr, int64(b)) 59 | } 60 | -------------------------------------------------------------------------------- /transaction/movnt.go: -------------------------------------------------------------------------------- 1 | // +build amd64 2 | package transaction 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "unsafe" 8 | ) 9 | 10 | func movnt4x64b(dst, src uintptr) // 256 bytes 11 | func movnt2x64b(dst, src uintptr) // 128 bytes 12 | func movnt1x64b(dst, src uintptr) // 64 bytes 13 | func movnt1x32b(dst, src uintptr) // 32 bytes 14 | func movnt1x16b(dst, src uintptr) // 16 bytes 15 | func movnt1x8b(dst, src uintptr) // 8 bytes 16 | func movnt1x4b(dst, src uintptr) // 4 bytes 17 | 18 | // issue clwb, but no fence 19 | func memmove_small_clwb(dst, src, len uintptr) { 20 | if len == 0 { 21 | return 22 | } 23 | srcByte := (*[maxInt]byte)(unsafe.Pointer(src)) 24 | dstByte := (*[maxInt]byte)(unsafe.Pointer(dst)) 25 | copy(dstByte[:len], srcByte[:len]) 26 | runtime.FlushRange(unsafe.Pointer(dst), len) 27 | return 28 | } 29 | 30 | // issue movnt if we can, else fall back to clwb 31 | func memmove_small(dst, src, len uintptr) { 32 | if len > 15 { 33 | panic(fmt.Sprintf("[movnt.go] [memmove_small] len is %d should be using movnt first", len)) 34 | } 35 | align := len & 7 36 | if len != 0 && align == 0 { 37 | movnt1x8b(dst, src) 38 | dst += 8 39 | src += 8 40 | len -= 8 41 | } 42 | align = len & 3 43 | for len != 0 && align == 0 { 44 | movnt1x4b(dst, src) 45 | dst += 4 46 | src += 4 47 | len -= 4 48 | } 49 | // We cannot issue movnt because either the data structure is < 4B at this 50 | // point or it is unaligned. Let's issue clwb. 51 | memmove_small_clwb(dst, src, len) 52 | } 53 | 54 | // caller needs to put memory barrier to ensure ordering of stores 55 | func movnt(dst0, src0 unsafe.Pointer, len uintptr) { 56 | dst := uintptr(dst0) 57 | src := uintptr(src0) 58 | if len <= 15 { 59 | memmove_small(dst, src, len) 60 | return 61 | } 62 | 63 | align := dst & 15 // Make sure we start with 16B align 64 | if align > 0 { 65 | memmove_small(dst, src, align) 66 | dst += align 67 | src += align 68 | len -= align 69 | } 70 | for len >= 16 { 71 | movnt1x16b(dst, src) 72 | dst += 16 73 | src += 16 74 | len -= 16 75 | } 76 | memmove_small(dst, src, len) 77 | } 78 | -------------------------------------------------------------------------------- /transaction/movnt.s: -------------------------------------------------------------------------------- 1 | // +build amd64 2 | 3 | // Move 256 bytes. This involves 16 16-byte copy 4 | TEXT ·movnt4x64b(SB), $0 5 | // Bytes 0 to 15 6 | MOVQ y+8(FP), AX // AX = src 7 | // movdqu %(rax), %xmm0 OR __m128i xmm0 = _mm_loadu_si128((__m128i *)src); 8 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 9 | //movntdq %xmm0, (%rbx) OR _mm_stream_si128((__m128i *)dest, xmm0); 10 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 11 | 12 | // Bytes 16 to 31 13 | ADDQ $0x10, AX 14 | ADDQ $0x10, BX 15 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 16 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 17 | 18 | // Bytes 32 to 47 19 | ADDQ $0x10, AX 20 | ADDQ $0x10, BX 21 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 22 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 23 | 24 | // Bytes 48 to 63 25 | ADDQ $0x10, AX 26 | ADDQ $0x10, BX 27 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 28 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 29 | 30 | // Bytes 64 to 79 31 | ADDQ $0x10, AX 32 | ADDQ $0x10, BX 33 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 34 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 35 | 36 | // Bytes 80 to 95 37 | ADDQ $0x10, AX 38 | ADDQ $0x10, BX 39 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 40 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 41 | 42 | // Bytes 96 to 111 43 | ADDQ $0x10, AX 44 | ADDQ $0x10, BX 45 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 46 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 47 | 48 | // Bytes 112 to 127 49 | ADDQ $0x10, AX 50 | ADDQ $0x10, BX 51 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 52 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 53 | 54 | // Bytes 128 to 143 55 | ADDQ $0x10, AX 56 | ADDQ $0x10, BX 57 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 58 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 59 | 60 | // Bytes 144 to 159 61 | ADDQ $0x10, AX 62 | ADDQ $0x10, BX 63 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 64 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 65 | 66 | // Bytes 160 to 175 67 | ADDQ $0x10, AX 68 | ADDQ $0x10, BX 69 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 70 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 71 | 72 | // Bytes 176 to 191 73 | ADDQ $0x10, AX 74 | ADDQ $0x10, BX 75 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 76 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 77 | 78 | // Bytes 192 to 207 79 | ADDQ $0x10, AX 80 | ADDQ $0x10, BX 81 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 82 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 83 | 84 | // Bytes 208 to 223 85 | ADDQ $0x10, AX 86 | ADDQ $0x10, BX 87 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 88 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 89 | 90 | // Bytes 224 to 239 91 | ADDQ $0x10, AX 92 | ADDQ $0x10, BX 93 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 94 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 95 | 96 | // Bytes 240 to 255 97 | ADDQ $0x10, AX 98 | ADDQ $0x10, BX 99 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 100 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 101 | 102 | RET 103 | 104 | // Move 128 bytes. This involves 8 16-byte copy 105 | TEXT ·movnt2x64b(SB), $0 106 | // Bytes 0 to 15 107 | MOVQ y+8(FP), AX // AX = src 108 | // movdqu %(rax), %xmm0 OR __m128i xmm0 = _mm_loadu_si128((__m128i *)src); 109 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 110 | //movntdq %xmm0, (%rbx) OR _mm_stream_si128((__m128i *)dest, xmm0); 111 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 112 | 113 | // Bytes 16 to 31 114 | ADDQ $0x10, AX 115 | ADDQ $0x10, BX 116 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 117 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 118 | 119 | // Bytes 32 to 47 120 | ADDQ $0x10, AX 121 | ADDQ $0x10, BX 122 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 123 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 124 | 125 | // Bytes 48 to 63 126 | ADDQ $0x10, AX 127 | ADDQ $0x10, BX 128 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 129 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 130 | 131 | // Bytes 64 to 79 132 | ADDQ $0x10, AX 133 | ADDQ $0x10, BX 134 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 135 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 136 | 137 | // Bytes 80 to 95 138 | ADDQ $0x10, AX 139 | ADDQ $0x10, BX 140 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 141 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 142 | 143 | // Bytes 96 to 111 144 | ADDQ $0x10, AX 145 | ADDQ $0x10, BX 146 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 147 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 148 | 149 | // Bytes 112 to 127 150 | ADDQ $0x10, AX 151 | ADDQ $0x10, BX 152 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 153 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 154 | 155 | RET 156 | 157 | // Move 64 bytes. This involves 4 16-byte copy 158 | TEXT ·movnt1x64b(SB), $0 159 | // Bytes 0 to 15 160 | MOVQ y+8(FP), AX // AX = src 161 | // movdqu %(rax), %xmm0 OR __m128i xmm0 = _mm_loadu_si128((__m128i *)src); 162 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 163 | //movntdq %xmm0, (%rbx) OR _mm_stream_si128((__m128i *)dest, xmm0); 164 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 165 | 166 | // Bytes 16 to 31 167 | ADDQ $0x10, AX 168 | ADDQ $0x10, BX 169 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 170 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 171 | 172 | // Bytes 32 to 47 173 | ADDQ $0x10, AX 174 | ADDQ $0x10, BX 175 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 176 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 177 | 178 | // Bytes 48 to 63 179 | ADDQ $0x10, AX 180 | ADDQ $0x10, BX 181 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 182 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 183 | 184 | RET 185 | 186 | // Move 32 bytes. This involves 2 16-byte copy 187 | TEXT ·movnt1x32b(SB), $0 188 | // Bytes 0 to 15 189 | MOVQ y+8(FP), AX // AX = src 190 | // movdqu %(rax), %xmm0 OR __m128i xmm0 = _mm_loadu_si128((__m128i *)src); 191 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 192 | //movntdq %xmm0, (%rbx) OR _mm_stream_si128((__m128i *)dest, xmm0); 193 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 194 | 195 | // Bytes 16 to 31 196 | ADDQ $0x10, AX 197 | ADDQ $0x10, BX 198 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 199 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 200 | 201 | RET 202 | 203 | // Move 16 bytes 204 | TEXT ·movnt1x16b(SB), $0 205 | MOVQ x+0(FP), BX // *dest 206 | MOVQ y+8(FP), AX // *src 207 | // movdqu %(rax), %xmm0 OR __m128i xmm0 = _mm_loadu_si128((__m128i *)src); 208 | BYTE $0xF3; BYTE $0x0F; BYTE $0x6F; BYTE $0x00 209 | //movntdq %xmm0, (%rbx) OR _mm_stream_si128((__m128i *)dest, xmm0); 210 | BYTE $0x66; BYTE $0x0f; BYTE $0xe7; BYTE $0x03 211 | RET 212 | 213 | // Move 8 bytes 214 | TEXT ·movnt1x8b(SB), $0 215 | MOVQ x+0(FP), BX // BX = dest 216 | MOVQ y+8(FP), AX // AX = src 217 | MOVQ (AX), DX // DX = *src 218 | MOVQ BX, AX // AX = dest 219 | // movnti %rdx, %(rax) // *dest = DX 220 | BYTE $0x48; BYTE $0x0f; BYTE $0xc3; BYTE $0x10 221 | RET 222 | 223 | // Move 4 bytes 224 | TEXT ·movnt1x4b(SB), $0 225 | MOVQ x+0(FP), BX // BX = dest 226 | MOVQ y+8(FP), AX // AX = src 227 | MOVL (AX), DX // DX = *src lower 32-b 228 | MOVQ BX, AX // AX = dest 229 | // movnti %edx, %(rax) 230 | BYTE $0x0f; BYTE $0xc3; BYTE $0x10 231 | RET 232 | -------------------------------------------------------------------------------- /transaction/optEnd.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "runtime" 5 | "unsafe" 6 | ) 7 | 8 | var cacheLineSz = uintptr(64) 9 | 10 | type ( 11 | flushSt struct { 12 | data map[unsafe.Pointer]uintptr 13 | } 14 | ) 15 | 16 | // set bits i to j in num as 1. i, j inclusive and start from 0 17 | func setBits(num, i, j uintptr) uintptr { 18 | if j > 63 { 19 | j = 63 20 | } 21 | // calculating a number 'n' having set 22 | // bits in the range from i to j and all other 23 | // bits as 0 (or unset). 24 | n := (((1 << i) - 1) ^ ((1 << (j + 1)) - 1)) 25 | return num | uintptr(n) 26 | } 27 | 28 | // return if bits i to j in num are all set to 1. i, j inclusive and start from 0 29 | func getBits(num, i, j uintptr) bool { 30 | if j > 63 { 31 | j = 63 32 | } 33 | n := (uintptr)(((1 << (i)) - 1) ^ ((1 << (j + 1)) - 1)) 34 | m := num & n 35 | if m == n { 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | func (f *flushSt) insert(start, size uintptr) bool { 42 | if f.data == nil { 43 | // This is kept as a map of unsafe pointers so that GC walks the map 44 | f.data = make(map[unsafe.Pointer]uintptr) 45 | } 46 | exists := true 47 | alignedAddr := start &^ (cacheLineSz - 1) 48 | lower8Bits := start & (cacheLineSz - 1) 49 | 50 | // We only care about cacheline aligned addresses 51 | 52 | sizeRemain := size 53 | for alignedAddr < start+size { 54 | currMask, ok := f.data[unsafe.Pointer(alignedAddr)] 55 | if !ok { 56 | exists = false 57 | f.data[unsafe.Pointer(alignedAddr)] = setBits(0, lower8Bits, lower8Bits+sizeRemain) 58 | } else { 59 | if getBits(currMask, lower8Bits, lower8Bits+sizeRemain) == false { 60 | exists = false 61 | f.data[unsafe.Pointer(alignedAddr)] = setBits(currMask, lower8Bits, lower8Bits+sizeRemain) 62 | } 63 | } 64 | alignedAddr += cacheLineSz 65 | sizeRemain -= cacheLineSz 66 | } 67 | return exists 68 | } 69 | 70 | func (f *flushSt) flushAndDestroy() { 71 | if f.data != nil { 72 | flushRbTreeMap(f.data) 73 | runtime.Fence() 74 | f.data = nil 75 | } 76 | } 77 | 78 | // only destroys 79 | func (f *flushSt) Destroy() { 80 | f.data = nil 81 | } 82 | 83 | func flushRbTreeMap(m map[unsafe.Pointer]uintptr) { 84 | for k := range m { 85 | runtime.FlushRange(k, cacheLineSz) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /transaction/redoTx.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | /* Put address of the data to be updated and the new data entry in persistent 7 | * heap (with type info). All changes are copied to in-place data structures 8 | * at end of transaction. Value-based logging is done, so in-place updates 9 | * are idempotent & can be reperformed from the start in case of nested crashes. 10 | * E.g.: 11 | * type S struct { 12 | * P *int 13 | * } 14 | * tx := NewredoTx() 15 | * tx.Begin() 16 | * tx.Log(S.P, 100) 17 | * a := tx.ReadLog(S.P) // At this point, a = 100, but S.P = 0 18 | * tx.End() // S.P = 100 after this 19 | * transaction.Release(tx) 20 | * 21 | * 22 | * | TxHeader | // Pointer to header passed & stored as 23 | * --------- | ------ ----- // part of application pmem root 24 | * | | | 25 | * V V V 26 | * --------------------------- 27 | * | logPtr | logPtr | ... | // Stored in pmem. Pointers to logs 28 | * --------------------------- 29 | * | 30 | * | --------------------- 31 | * ---> | entry | entry | ... | // Has address of updates & new data copy 32 | * --------------------- 33 | * | 34 | * | ----------- 35 | * ----> | data copy | // Single copy for each address 36 | * ----------- // In case of multiple updates to one 37 | * // address, this copy is updated 38 | */ 39 | 40 | package transaction 41 | 42 | import ( 43 | "errors" 44 | "log" 45 | "reflect" 46 | "runtime" 47 | "runtime/debug" 48 | "sync" 49 | "unsafe" 50 | ) 51 | 52 | type ( 53 | redoTx struct { 54 | log []entry 55 | 56 | // stores the tail position of the log where new data would be stored 57 | tail int 58 | 59 | // Level of nesting. Needed for nested transactions 60 | level int 61 | 62 | // Index of the log handle within redoArray 63 | index int 64 | 65 | // num of entries which can be stored in log 66 | nEntry int 67 | committed bool 68 | m map[unsafe.Pointer]int 69 | rlocks []*sync.RWMutex 70 | wlocks []*sync.RWMutex 71 | 72 | // record which log entries store sliceheader, and store the size of 73 | // each element in that slice. This is only used when transaction ends 74 | // successfully. So this structure is stored in volatile memory. 75 | storeSliceHdr []pair 76 | } 77 | 78 | redoTxHeader struct { 79 | magic int 80 | logPtr [logNum]*redoTx 81 | } 82 | ) 83 | 84 | var ( 85 | headerPtr *redoTxHeader 86 | redoArray *bitmap 87 | ) 88 | 89 | /* Does the first time initialization, else restores log structure and 90 | * flushed committed logs. Returns the pointer to redoTX internal structure, 91 | * so the application can store this in its pmem appRoot. 92 | */ 93 | func initRedoTx(logHeadPtr unsafe.Pointer) unsafe.Pointer { 94 | redoArray = newBitmap(logNum) 95 | if logHeadPtr == nil { 96 | // First time initialization 97 | headerPtr = pnew(redoTxHeader) 98 | for i := 0; i < logNum; i++ { 99 | headerPtr.logPtr[i] = _initRedoTx(NumEntries, i) 100 | } 101 | // Write the magic constant after the transaction handles are persisted. 102 | // NewRedoTx() can then check this constant to ensure all tx handles 103 | // are properly initialized before releasing any. 104 | runtime.PersistRange(unsafe.Pointer(&headerPtr.logPtr), logNum*ptrSize) 105 | headerPtr.magic = magic 106 | runtime.PersistRange(unsafe.Pointer(&headerPtr.magic), ptrSize) 107 | logHeadPtr = unsafe.Pointer(headerPtr) 108 | } else { 109 | headerPtr = (*redoTxHeader)(logHeadPtr) 110 | if headerPtr.magic != magic { 111 | log.Fatal("redoTxHeader magic does not match!") 112 | } 113 | 114 | // Depending on committed status of transactions, flush changes to 115 | // data structures or delete all log entries. 116 | var tx *redoTx 117 | for i := 0; i < logNum; i++ { 118 | tx = headerPtr.logPtr[i] 119 | tx.index = i 120 | tx.wlocks = make([]*sync.RWMutex, 0, 0) // Resetting volatile locks 121 | tx.rlocks = make([]*sync.RWMutex, 0, 0) // before checking for data 122 | tx.storeSliceHdr = make([]pair, 0, 0) 123 | if tx.committed { 124 | tx.commit(true) 125 | } else { 126 | tx.abort() 127 | } 128 | } 129 | } 130 | 131 | return logHeadPtr 132 | } 133 | 134 | func _initRedoTx(size, index int) *redoTx { 135 | tx := pnew(redoTx) 136 | tx.nEntry = size 137 | tx.log = pmake([]entry, size) 138 | runtime.PersistRange(unsafe.Pointer(tx), unsafe.Sizeof(*tx)) 139 | tx.index = index 140 | tx.m = make(map[unsafe.Pointer]int) // On abort m isn't used, so not in pmem 141 | tx.wlocks = make([]*sync.RWMutex, 0, 0) 142 | tx.rlocks = make([]*sync.RWMutex, 0, 0) 143 | tx.storeSliceHdr = make([]pair, 0, 0) 144 | return tx 145 | } 146 | 147 | func NewRedoTx() TX { 148 | if headerPtr == nil || headerPtr.magic != magic { 149 | log.Fatal("redo log not correctly initialized!") 150 | } 151 | index := redoArray.nextAvailable() 152 | return headerPtr.logPtr[index] 153 | } 154 | 155 | func releaseRedoTx(t *redoTx) { 156 | t.abort() 157 | redoArray.clearBit(t.index) 158 | } 159 | 160 | func (t *redoTx) ReadLog(intf ...interface{}) (retVal interface{}) { 161 | if len(intf) == 2 { 162 | return t.readSliceElem(intf[0], intf[1].(int)) 163 | } else if len(intf) == 3 { 164 | return t.readSlice(intf[0], intf[1].(int), intf[2].(int)) 165 | } else if len(intf) != 1 { 166 | panic("[redoTx] ReadLog: Incorrect number of args passed") 167 | } 168 | ptrV := reflect.ValueOf(intf[0]) 169 | switch ptrV.Kind() { 170 | case reflect.Ptr: 171 | oldVal := reflect.Indirect(ptrV) 172 | var logData reflect.Value 173 | if oldVal.Kind() == reflect.Struct { 174 | // construct struct by reading each field from log 175 | typ := oldVal.Type() 176 | retStructPtr := reflect.New(typ) 177 | retStruct := retStructPtr.Elem() 178 | for i := 0; i < oldVal.NumField(); i++ { 179 | structFieldPtr := oldVal.Field(i).Addr() 180 | structFieldTyp := typ.Field(i).Type 181 | if structFieldTyp.Kind() == reflect.Struct { 182 | // Handle nested struct 183 | v1 := t.ReadLog(structFieldPtr.Interface()) 184 | logData = reflect.ValueOf(v1) 185 | } else { 186 | logData = t.readLogEntry(structFieldPtr.Pointer(), 187 | structFieldTyp) 188 | } 189 | if retStruct.Field(i).CanSet() { 190 | retStruct.Field(i).Set(logData) // populate struct field 191 | } else { 192 | log.Fatal("[redoTx] ReadLog: Cannot read struct with " + 193 | "unexported field") 194 | } 195 | } 196 | retVal = retStruct.Interface() 197 | } else if oldVal.Kind() == reflect.Invalid { 198 | // Do nothing. 199 | } else { 200 | typ := oldVal.Type() 201 | logData = t.readLogEntry(ptrV.Pointer(), typ) 202 | retVal = logData.Interface() 203 | } 204 | default: 205 | log.Fatal("[redoTx] ReadLog: Arg must be pointer") 206 | } 207 | return retVal 208 | } 209 | 210 | func (t *redoTx) readLogEntry(ptr uintptr, typ reflect.Type) (v reflect.Value) { 211 | tail, ok := t.m[unsafe.Pointer(ptr)] 212 | if !ok { 213 | dataPtr := reflect.NewAt(typ, unsafe.Pointer(ptr)) 214 | v = reflect.Indirect(dataPtr) 215 | // TODO: Data was not stored in redo log before. Should we log now? 216 | return v 217 | } 218 | logDataPtr := reflect.NewAt(typ, t.log[tail].data) 219 | v = reflect.Indirect(logDataPtr) 220 | return v 221 | } 222 | 223 | func (t *redoTx) readSliceElem(slicePtr interface{}, index int) interface{} { 224 | var retVal reflect.Value 225 | ptrV := reflect.ValueOf(slicePtr) 226 | sTyp := ptrV.Type().Elem() // type of slice 227 | switch ptrV.Kind() { 228 | case reflect.Ptr: 229 | logData := t.readLogEntry(ptrV.Pointer(), sTyp) // read sliceheader 1st 230 | v := (*value)(unsafe.Pointer(&logData)) 231 | newShdr := (*sliceHeader)(v.ptr) 232 | if index > newShdr.len { 233 | log.Fatal("[redoTx] readSliceElem: Index out of bounds") 234 | } 235 | elemType := sTyp.Elem() // type of elements in slice 236 | elemPtr := uintptr(newShdr.data) + (uintptr(index) * elemType.Size()) 237 | retVal = t.readLogEntry(elemPtr, elemType) 238 | default: 239 | log.Fatal("[redoTx] readSliceElem: Arg must be pointer to slice") 240 | } 241 | return retVal.Interface() 242 | } 243 | 244 | func (t *redoTx) readSlice(slicePtr interface{}, stIndex int, 245 | endIndex int) interface{} { 246 | var retVal reflect.Value 247 | ptrV := reflect.ValueOf(slicePtr) 248 | sTyp := ptrV.Type().Elem() // type of slice 249 | switch ptrV.Kind() { 250 | case reflect.Ptr: 251 | logData := t.readLogEntry(ptrV.Pointer(), sTyp) // read sliceheader 1st 252 | v := (*value)(unsafe.Pointer(&logData)) 253 | newShdr := (*sliceHeader)(v.ptr) 254 | origL := newShdr.len 255 | if stIndex < 0 || endIndex > origL { 256 | log.Fatal("[redoTx] readSlice: Index out of bounds") 257 | } else if stIndex > endIndex { 258 | log.Fatal("[redoTx] readSlice: 1st index should be <= 2nd index") 259 | } 260 | elemType := sTyp.Elem() // type of elements in slice 261 | sLen := endIndex - stIndex 262 | s := reflect.PMakeSlice(sTyp, sLen, sLen) 263 | for i := 0; i < sLen; i++ { 264 | sElem := s.Index(i) 265 | elemPtr := uintptr(newShdr.data) + (uintptr(stIndex+i) * 266 | elemType.Size()) 267 | sElem.Set(t.readLogEntry(elemPtr, elemType)) 268 | } 269 | retVal = s 270 | default: 271 | log.Fatal("[redoTx] readSlice: Arg must be pointer to slice") 272 | } 273 | return retVal.Interface() 274 | } 275 | 276 | func checkDataTypes(newV reflect.Value, v1 reflect.Value) (err error) { 277 | if newV.Kind() != reflect.Invalid { 278 | // Invalid => Logging value. No type check 279 | oldV := reflect.Indirect(v1) 280 | if oldV.Kind() != reflect.Interface { 281 | // Can't do type comparison if logging interface 282 | oldType := v1.Type().Elem() 283 | if newV.Type() != oldType { 284 | err = errors.New("Log Error. Data passed to Log() is not of " + 285 | "the same type as underlying data of ptr") 286 | } 287 | } 288 | } 289 | return err 290 | } 291 | 292 | func (t *redoTx) Log3(src unsafe.Pointer, size uintptr) error { 293 | log.Fatal("Not implemented") 294 | return nil 295 | } 296 | 297 | func (t *redoTx) Log2(src, dst unsafe.Pointer, size uintptr) error { 298 | log.Fatal("Not implemented") 299 | return nil 300 | } 301 | 302 | // Caveat: With the current implementation, Redo Log doesn't support logging 303 | // structs with unexported slice, struct, interface members. Individual fields 304 | // of struct can be logged. 305 | func (t *redoTx) Log(intf ...interface{}) (err error) { 306 | if len(intf) != 2 { 307 | return errors.New("[redoTx] Log: Incorrectly called. Correct usage: " + 308 | "Log(ptr, data)") 309 | } 310 | v1 := reflect.ValueOf(intf[0]) 311 | v2 := reflect.ValueOf(intf[1]) 312 | switch kind := v1.Kind(); kind { 313 | case reflect.Slice: 314 | if v2.Type() != v1.Type() { 315 | return errors.New("Log Error. Slice values passed to Log() are " + 316 | "not of the same type") 317 | } 318 | 319 | // Each slice element is stored separately in log 320 | var vLen int 321 | if v1.Len() < v2.Len() { 322 | vLen = v1.Len() 323 | } else { 324 | vLen = v2.Len() 325 | } 326 | for i := 0; i < vLen; i++ { 327 | elemNewVal := v2.Index(i) 328 | elemPtr := v1.Index(i).Addr() 329 | t.writeLogEntry(elemPtr.Pointer(), elemNewVal, elemNewVal.Type()) 330 | } 331 | case reflect.Ptr: 332 | oldType := v1.Type().Elem() // type of data, v1 is pointing to 333 | err = checkDataTypes(v2, v1) 334 | if err != nil { 335 | return err 336 | } 337 | if v2.Kind() == reflect.Struct { 338 | // Each struct field is stored separately in log 339 | oldV := reflect.Indirect(v1) 340 | for i := 0; i < v2.NumField(); i++ { 341 | newVal := v2.Field(i) 342 | oldVal := oldV.Field(i) 343 | oldType = oldVal.Type() 344 | ptrOrig := oldVal.Addr() 345 | if newVal.Kind() == reflect.Struct { 346 | if !newVal.CanInterface() { 347 | log.Fatal("[redoTx] Log: Cannot log unexported struct" + 348 | "member variable") 349 | } 350 | err = t.Log(ptrOrig.Interface(), newVal.Interface()) 351 | } else if newVal.Kind() == reflect.Slice { 352 | if !newVal.CanInterface() { 353 | log.Fatal("[redoTx] Log: Cannot log unexported slice" + 354 | "member variable") 355 | } 356 | err = t.Log(oldVal.Interface(), newVal.Interface()) 357 | } else { 358 | err = t.writeLogEntry(ptrOrig.Pointer(), newVal, oldType) 359 | } 360 | if err != nil { 361 | return err 362 | } 363 | } 364 | } else { 365 | err = t.writeLogEntry(v1.Pointer(), v2, oldType) 366 | } 367 | default: 368 | debug.PrintStack() 369 | return errors.New("[redoTx] Log: first arg must be pointer/slice") 370 | } 371 | if !runtime.InPmem(v1.Pointer()) { 372 | err = errors.New("[redoTx] Log: Updates to data in volatile memory" + 373 | " can be lost") 374 | } 375 | return err 376 | } 377 | 378 | func (t *redoTx) writeLogEntry(ptr uintptr, data reflect.Value, 379 | typ reflect.Type) error { 380 | size := int(typ.Size()) 381 | var logDataPtr reflect.Value 382 | logDataPtr = reflect.PNew(typ) 383 | if data.Kind() == reflect.Invalid { 384 | // Do nothing, data has value 385 | } else if data.CanInterface() { 386 | reflect.Indirect(logDataPtr).Set(data) 387 | } else { // To log unexported fields of struct 388 | switch data.Kind() { 389 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 390 | reflect.Int64: 391 | reflect.Indirect(logDataPtr).SetInt(data.Int()) 392 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 393 | reflect.Uint64, reflect.Uintptr: 394 | reflect.Indirect(logDataPtr).SetUint(data.Uint()) 395 | case reflect.Float32, reflect.Float64: 396 | reflect.Indirect(logDataPtr).SetFloat(data.Float()) 397 | case reflect.Bool: 398 | reflect.Indirect(logDataPtr).SetBool(data.Bool()) 399 | case reflect.Complex64, reflect.Complex128: 400 | reflect.Indirect(logDataPtr).SetComplex(data.Complex()) 401 | case reflect.String: 402 | reflect.Indirect(logDataPtr).SetString(data.String()) 403 | case reflect.Ptr, reflect.UnsafePointer: 404 | // Go only allows setting a pointer as unsafe.Pointer, so logDataPtr 405 | // has to be reallocated with new type as unsafe.Pointer 406 | ptrData := unsafe.Pointer(data.Pointer()) 407 | logDataPtr = reflect.PNew(reflect.TypeOf(ptrData)) 408 | reflect.Indirect(logDataPtr).SetPointer(ptrData) 409 | default: 410 | log.Fatalf("[redoTx] Log: Cannot log unexported data of kind: ", 411 | data.Kind(), "type: ", typ) 412 | } 413 | } 414 | 415 | // Check if write to this addr already stored in log by checking in map. 416 | // If yes, update value in-place in log. Else add new entry to log. 417 | tail, ok := t.m[unsafe.Pointer(ptr)] 418 | if !ok { 419 | tail = t.tail 420 | t.m[unsafe.Pointer(ptr)] = t.tail 421 | 422 | // Update log offset in header. 423 | t.tail++ 424 | if t.tail >= t.nEntry { // Expand log if necessary 425 | newE := 2 * t.nEntry 426 | newLog := pmake([]entry, newE) 427 | copy(newLog, t.log) 428 | t.log = newLog 429 | t.nEntry = newE 430 | } 431 | } 432 | 433 | // Update log to have addr of original data, addr of new copy & size of data 434 | t.log[tail].ptr = unsafe.Pointer(ptr) 435 | t.log[tail].data = unsafe.Pointer(logDataPtr.Pointer()) 436 | t.log[tail].size = size 437 | if data.Kind() == reflect.Slice { 438 | // ptr to sliceHeader was passed for logging, so need to persist 439 | // new slice too on tx complete. This will be used in t.commit() 440 | t.storeSliceHdr = append(t.storeSliceHdr, pair{tail, int(data.Type().Elem().Size())}) 441 | } 442 | return nil 443 | } 444 | 445 | /* Exec function receives a variable number of interfaces as its arguments. 446 | * Usage: Exec(fn_name, fn_arg1, fn_arg2, ...) 447 | * Function fn_name() should not return anything. 448 | * No need to Begin() & End() transaction separately if Exec() is used. 449 | * Caveat: All locks within function fn_name(fn_arg1, fn_arg2, ...) should be 450 | * taken before making Exec() call. Locks should be released after Exec() call. 451 | */ 452 | func (t *redoTx) Exec(intf ...interface{}) (retVal []reflect.Value, err error) { 453 | if len(intf) < 1 { 454 | return retVal, 455 | errors.New("[redoTx] Exec: Must have atleast one argument") 456 | } 457 | fnPosInInterfaceArgs := 0 458 | fn := reflect.ValueOf(intf[fnPosInInterfaceArgs]) // The function to call 459 | if fn.Kind() != reflect.Func { 460 | return retVal, 461 | errors.New("[redoTx] Exec: 1st argument must be a function") 462 | } 463 | fnType := fn.Type() 464 | fnName := runtime.FuncForPC(fn.Pointer()).Name() 465 | // Populate the arguments of the function correctly 466 | argv := make([]reflect.Value, fnType.NumIn()) 467 | if len(argv) != len(intf) { 468 | return retVal, errors.New("[redoTx] Exec: Incorrect no. of args to " + 469 | "function " + fnName) 470 | } 471 | for i := range argv { 472 | if i == fnPosInInterfaceArgs { 473 | // Add t *redoTx as the 1st argument to be passed to the function 474 | // fn. This is not passed by the application when it calls Exec(). 475 | argv[i] = reflect.ValueOf(t) 476 | } else { 477 | // get the arguments to the function call from the call to Exec() 478 | // and populate in argv 479 | if reflect.TypeOf(intf[i]) != fnType.In(i) { 480 | return retVal, errors.New("[redoTx] Exec: Incorrect type of " + 481 | "args to function " + fnName) 482 | } 483 | argv[i] = reflect.ValueOf(intf[i]) 484 | } 485 | } 486 | t.Begin() 487 | defer t.End() 488 | 489 | txLevel := t.level 490 | retVal = fn.Call(argv) 491 | if txLevel != t.level { 492 | return retVal, errors.New("[redoTx] Exec: Unbalanced Begin() & End() " + 493 | "calls inside function " + fnName) 494 | } 495 | return retVal, err 496 | } 497 | 498 | func (t *redoTx) Begin() error { 499 | t.level++ 500 | return nil 501 | } 502 | 503 | /* Persists the update written to redoLog during the transaction lifetime. For 504 | * nested transactions, End() call to inner transaction does nothing. Returns a 505 | * bool indicating if it is safe to release the transaction handle. 506 | */ 507 | func (t *redoTx) End() bool { 508 | if t.level == 0 { 509 | return true 510 | } 511 | t.level-- 512 | if t.level == 0 { 513 | // Flush changes in log. Mark tx as committed. Call commit() 514 | // to transfer changes to app data structures 515 | for i := t.tail - 1; i >= 0; i-- { 516 | runtime.PersistRange(t.log[i].data, uintptr(t.log[i].size)) 517 | } 518 | runtime.PersistRange(unsafe.Pointer(&t.log[0]), 519 | uintptr(t.tail*(int)(unsafe.Sizeof(t.log[0])))) 520 | runtime.PersistRange(unsafe.Pointer(t), unsafe.Sizeof(*t)) 521 | t.committed = true 522 | runtime.PersistRange(unsafe.Pointer(&t.committed), 523 | unsafe.Sizeof(t.committed)) 524 | t.commit(false) 525 | return true 526 | } 527 | return false 528 | } 529 | 530 | func (t *redoTx) RLock(m *sync.RWMutex) { 531 | m.RLock() 532 | t.rlocks = append(t.rlocks, m) 533 | } 534 | 535 | func (t *redoTx) WLock(m *sync.RWMutex) { 536 | m.Lock() 537 | t.wlocks = append(t.wlocks, m) 538 | } 539 | 540 | func (t *redoTx) Lock(m *sync.RWMutex) { 541 | t.WLock(m) 542 | } 543 | 544 | func (t *redoTx) unLock() { 545 | for i, m := range t.wlocks { 546 | m.Unlock() 547 | t.wlocks[i] = nil 548 | } 549 | t.wlocks = t.wlocks[:0] 550 | for i, m := range t.rlocks { 551 | m.RUnlock() 552 | t.rlocks[i] = nil 553 | } 554 | t.rlocks = t.rlocks[:0] 555 | } 556 | 557 | // Performs in-place updates of app data structures. Started again, if crashed 558 | // in between 559 | func (t *redoTx) commit(skipVolData bool) error { 560 | j := 0 561 | for i := 0; i < t.tail; i++ { 562 | oldDataPtr := (*[maxInt]byte)(t.log[i].ptr) 563 | if skipVolData && !runtime.InPmem(uintptr(unsafe.Pointer(oldDataPtr))) { 564 | // If commit() was called during Init, control reaches here. If so, 565 | // we drop updates to data in volatile memory 566 | continue 567 | } 568 | logDataPtr := (*[maxInt]byte)(t.log[i].data) 569 | oldData := oldDataPtr[:t.log[i].size:t.log[i].size] 570 | logData := logDataPtr[:t.log[i].size:t.log[i].size] 571 | copy(oldData, logData) 572 | runtime.PersistRange(t.log[i].ptr, uintptr(t.log[i].size)) 573 | if j < len(t.storeSliceHdr) && t.storeSliceHdr[j].first == i { 574 | // ptr points to sliceHeader. So, need to persist the slice too 575 | shdr := (*sliceHeader)(t.log[i].ptr) 576 | runtime.PersistRange(shdr.data, uintptr(shdr.len* 577 | t.storeSliceHdr[j].second)) 578 | j++ 579 | } 580 | } 581 | t.committed = false 582 | runtime.PersistRange(unsafe.Pointer(&t.committed), 583 | unsafe.Sizeof(t.committed)) 584 | t.reset(t.tail) 585 | return nil 586 | } 587 | 588 | // Resets every entry in the log 589 | func (t *redoTx) abort() error { 590 | t.reset(t.tail) 591 | return nil 592 | } 593 | 594 | // Resets the entries from sz-1 to 0 in the log 595 | func (t *redoTx) reset(sz int) { 596 | defer t.unLock() 597 | t.level = 0 598 | t.m = make(map[unsafe.Pointer]int) 599 | t.log = t.log[:NumEntries] // reset to original size 600 | t.nEntry = NumEntries 601 | if sz > NumEntries { 602 | sz = NumEntries 603 | } 604 | for i := sz - 1; i >= 0; i-- { 605 | t.log[i].ptr = nil 606 | t.log[i].data = nil 607 | t.log[i].size = 0 608 | } 609 | t.tail = 0 610 | t.storeSliceHdr = make([]pair, 0, 0) 611 | } 612 | -------------------------------------------------------------------------------- /transaction/transaction.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | package transaction 7 | 8 | import ( 9 | "log" 10 | "reflect" 11 | "sync" 12 | "unsafe" 13 | ) 14 | 15 | const ( 16 | maxInt = 1<<31 - 1 17 | magic = 131071 18 | logNum = 512 19 | NumEntries = 128 20 | ptrSize = 8 // Size of an integer or pointer value in Go 21 | cacheSize = 64 22 | ) 23 | 24 | // transaction interface 25 | type ( 26 | TX interface { 27 | Begin() error 28 | Log(...interface{}) error 29 | Log2(src, dst unsafe.Pointer, size uintptr) error 30 | Log3(src unsafe.Pointer, size uintptr) error 31 | ReadLog(...interface{}) interface{} 32 | Exec(...interface{}) ([]reflect.Value, error) 33 | End() bool 34 | RLock(*sync.RWMutex) 35 | WLock(*sync.RWMutex) 36 | Lock(*sync.RWMutex) 37 | } 38 | 39 | // entry for each log update, stays in persistent heap. 40 | // ptr is the address of variable to be updated 41 | // data points to old data copy for undo log & new data for redo log 42 | entry struct { 43 | ptr unsafe.Pointer 44 | data unsafe.Pointer 45 | genNum int 46 | size int 47 | } 48 | ) 49 | 50 | func Init(logHeadPtr unsafe.Pointer, logType string) unsafe.Pointer { 51 | switch logType { 52 | case "undo": 53 | return initUndoTx(logHeadPtr) 54 | case "redo": 55 | return initRedoTx(logHeadPtr) 56 | default: 57 | log.Panic("initializing unsupported transaction! Try undo/redo") 58 | } 59 | return nil 60 | } 61 | 62 | func Release(t TX) { 63 | switch v := t.(type) { 64 | case *undoTx: 65 | releaseUndoTx(v) 66 | case *redoTx: 67 | releaseRedoTx(v) 68 | default: 69 | log.Panic("Releasing unsupported transaction!") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /transaction/undoTx.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | /* put log entries and data copies into persistent heap (with type info) 7 | * to prevent runtime garbage collection to reclaim dangling pointers caused by 8 | * undo updates. 9 | * E.g.: 10 | * type S struct { 11 | * P *int 12 | * } 13 | * tx := NewUndoTx() 14 | * tx.Begin() 15 | * tx.Log(&S.P) 16 | * S.P = nil 17 | * tx.End() 18 | * transaction.Release(tx) 19 | * 20 | * 21 | * | TxHeader | // Pointer to header passed & stored as 22 | * --------- | ------ ----- // part of application pmem root 23 | * | | | 24 | * V V V 25 | * --------------------------- 26 | * | logPtr | logPtr | ... | // Stored in pmem. Pointers to logs 27 | * --------------------------- 28 | * | 29 | * | --------------------- 30 | * ---> | entry | entry | ... | // Stored in pmem to track pointers to 31 | * --------------------- // data copies 32 | * | 33 | * | ----------- 34 | * ----> | data copy | // Stored in pmem to track pointers 35 | * ----------- // in data copies. e.g., S.P in 36 | * // previous example 37 | */ 38 | 39 | package transaction 40 | 41 | import ( 42 | "errors" 43 | "fmt" 44 | "log" 45 | "reflect" 46 | "runtime" 47 | "runtime/debug" 48 | "sync" 49 | "unsafe" 50 | ) 51 | 52 | type ( 53 | pair struct { 54 | first int 55 | second int 56 | } 57 | 58 | // Each undo log handle. Contains volatile metadata associated with the handle 59 | undoTx struct { 60 | tmpBuf [128]byte // A temporary buffer to copy data 61 | genNum uintptr // A volatile copy of the generation number 62 | 63 | // An array that holds all pointers found in the logged data in 64 | // so that they will be found by the GC. 65 | ptrArray []unsafe.Pointer 66 | 67 | // tail position of the log where new data would be stored 68 | tail int 69 | 70 | // Level of nesting. Needed for nested transactions 71 | level int 72 | 73 | // record which log entries store sliceheader, and store the size of 74 | // each element in that slice. 75 | // storeSliceHdr []pair // not used as Log() not supported 76 | 77 | // Pointer to the array in persistent memory where logged data is stored. 78 | // first points to the first linked array while curr points to the 79 | // currently used array which may or may not be equal to first. 80 | first *uLogData 81 | curr *uLogData 82 | 83 | // list of log entries which need not be flushed during transaction's 84 | // successful end. 85 | // skipList []int // not used as Log() not supported 86 | rlocks []*sync.RWMutex 87 | wlocks []*sync.RWMutex 88 | fs flushSt 89 | } 90 | 91 | // Actual undo log data residing in persistent memory 92 | uLogData struct { 93 | log []byte 94 | genNum uintptr // generation number of the logged data 95 | next *uLogData 96 | } 97 | 98 | undoTxHeader struct { 99 | magic int 100 | logData [logNum]uLogData 101 | } 102 | ) 103 | 104 | var ( 105 | txHeaderPtr *undoTxHeader 106 | undoArray *bitmap 107 | // zeroes is used to memset a cacheline size region of memory. It is sized 108 | // at 128 bytes as we want a 64-byte aligned region somewhere inside zeroes. 109 | zeroes [128]byte 110 | uHandles [logNum]undoTx 111 | ) 112 | 113 | const ( 114 | // Initial size of the undo log buffer in persistent memory 115 | uLogInitSize = 65536 116 | 117 | // Header data size before every undo log entry. Each undo log entry header 118 | // has the layout shown below, and occupies 24 bytes. 119 | // +--------+------+---------+-----------+ 120 | // | genNum | size | Pointer | Data | 121 | // | 8 | 8 | 8 | var-size | 122 | // +--------+------+---------+-----------+ 123 | uLogHdrSize = 24 124 | ) 125 | 126 | /* Does the first time initialization, else restores log structure and 127 | * reverts uncommitted logs. Does not do any pointer swizzle. This will be 128 | * handled by Go-pmem runtime. Returns the pointer to undoTX internal structure, 129 | * so the application can store it in its pmem appRoot. 130 | */ 131 | func initUndoTx(logHeadPtr unsafe.Pointer) unsafe.Pointer { 132 | undoArray = newBitmap(logNum) 133 | if logHeadPtr == nil { 134 | // First time initialization 135 | txHeaderPtr = pnew(undoTxHeader) 136 | initUndoHandles() 137 | // Write the magic constant after the transaction handles are persisted. 138 | // NewUndoTx() can then check this constant to ensure all tx handles 139 | // are properly initialized before releasing any. 140 | txHeaderPtr.magic = magic 141 | runtime.PersistRange(unsafe.Pointer(&txHeaderPtr.magic), ptrSize) 142 | logHeadPtr = unsafe.Pointer(txHeaderPtr) 143 | } else { 144 | txHeaderPtr = (*undoTxHeader)(logHeadPtr) 145 | if txHeaderPtr.magic != magic { 146 | log.Fatal("undoTxHeader magic does not match!") 147 | } 148 | 149 | // Recover data from previous pending transactions, if any 150 | for i := 0; i < logNum; i++ { 151 | handle := &txHeaderPtr.logData[i] 152 | uHandles[i].first = handle 153 | uHandles[i].genNum = handle.genNum 154 | uHandles[i].abort(false) 155 | } 156 | } 157 | 158 | return logHeadPtr 159 | } 160 | 161 | func initUndoHandles() { 162 | for i := 0; i < logNum; i++ { 163 | handle := &txHeaderPtr.logData[i] 164 | handle.genNum = 1 165 | handle.log = pmake([]byte, uLogInitSize) 166 | uHandles[i].first = handle 167 | uHandles[i].curr = handle 168 | uHandles[i].genNum = 1 169 | } 170 | 171 | runtime.PersistRange(unsafe.Pointer(&txHeaderPtr.logData), 172 | logNum*unsafe.Sizeof(txHeaderPtr.logData[0])) 173 | } 174 | 175 | func NewUndoTx() TX { 176 | if txHeaderPtr == nil || txHeaderPtr.magic != magic { 177 | log.Fatal("Undo log not correctly initialized!") 178 | } 179 | index := undoArray.nextAvailable() 180 | return &uHandles[index] 181 | } 182 | 183 | func releaseUndoTx(t *undoTx) { 184 | t.fs.Destroy() 185 | // Reset the pointers in the log entries, but need not allocate a new 186 | // backing array 187 | t.abort(false) 188 | index := (uintptr(unsafe.Pointer(t)) - uintptr(unsafe.Pointer(&uHandles[0]))) / 189 | unsafe.Sizeof(uHandles[0]) 190 | undoArray.clearBit(int(index)) 191 | } 192 | 193 | func (t *undoTx) setTail(tail int) { 194 | t.tail = tail 195 | } 196 | 197 | // resetLogData makes the undo handle point to the first element of the linked 198 | // list of undo logs. The pointer array is zeroed out to ensure those pointers 199 | // can get garbage collected. 200 | func (t *undoTx) resetLogData() { 201 | t.curr = t.first 202 | t.tail = 0 203 | 204 | // Zero out ptrArray 205 | len := len(t.ptrArray) 206 | if len == 0 { 207 | return 208 | } 209 | t.ptrArray[0] = nil 210 | for i := 1; i < len; i *= 2 { 211 | copy(t.ptrArray[i:], t.ptrArray[:i]) 212 | } 213 | t.ptrArray = t.ptrArray[:0] 214 | } 215 | 216 | // increaseLogSize creates an undo log buffer of size at least toAdd bytes and 217 | // links it to the existing linked list of log buffers. It also tries to reuse 218 | // any existing buffers that has sufficient capacity. 219 | func (t *undoTx) increaseLogSize(toAdd int) { 220 | uData := t.curr 221 | 222 | // We need at least toAdd bytes and current buffer do not have that much 223 | // space available. 224 | newCap := cap(uData.log) * 2 225 | if toAdd > newCap { 226 | // make sure newCap is a multiple of 64 227 | newCap = (64 * (toAdd + 63) / 64) 228 | } 229 | 230 | // Check if there is a linked log array with capacity at least 'toAdd'. 231 | if uData.next != nil { 232 | nextData := uData.next 233 | if cap(nextData.log) >= newCap { 234 | t.curr = nextData 235 | t.tail = 0 236 | return 237 | } 238 | } 239 | 240 | newLog := pnew(uLogData) 241 | newLog.log = pmake([]byte, newCap) 242 | runtime.PersistRange(unsafe.Pointer(newLog), 24) 243 | 244 | uData.next = newLog 245 | runtime.PersistRange(unsafe.Pointer(&uData.next), 8) 246 | t.curr = newLog 247 | t.tail = 0 248 | } 249 | 250 | type value struct { 251 | typ unsafe.Pointer 252 | ptr unsafe.Pointer 253 | flag uintptr 254 | } 255 | 256 | // sliceHeader is the datastructure representation of a slice object 257 | type sliceHeader struct { 258 | data unsafe.Pointer 259 | len int 260 | cap int 261 | } 262 | 263 | func (t *undoTx) ReadLog(intf ...interface{}) (retVal interface{}) { 264 | if len(intf) == 2 { 265 | return t.readSliceElem(intf[0], intf[1].(int)) 266 | } else if len(intf) == 3 { 267 | return t.readSlice(intf[0], intf[1].(int), intf[2].(int)) 268 | } else if len(intf) != 1 { 269 | panic("[undoTx] ReadLog: Incorrect number of args passed") 270 | } 271 | ptrV := reflect.ValueOf(intf[0]) 272 | switch ptrV.Kind() { 273 | case reflect.Ptr: 274 | data := reflect.Indirect(ptrV) 275 | retVal = data.Interface() 276 | default: 277 | panic("[undoTx] ReadLog: Arg must be pointer") 278 | } 279 | return retVal 280 | } 281 | 282 | func (t *undoTx) readSliceElem(slicePtr interface{}, index int) interface{} { 283 | ptrV := reflect.ValueOf(slicePtr) 284 | s := reflect.Indirect(ptrV) // get to the reflect.Value of slice first 285 | return s.Index(index).Interface() 286 | } 287 | 288 | func (t *undoTx) readSlice(slicePtr interface{}, stIndex int, 289 | endIndex int) interface{} { 290 | ptrV := reflect.ValueOf(slicePtr) 291 | s := reflect.Indirect(ptrV) // get to the reflect.Value of slice first 292 | return s.Slice(stIndex, endIndex).Interface() 293 | } 294 | 295 | // Log3 logs data in a linked list of byte arrays. 'src' is the pointer to the 296 | // data to be logged and 'size' is the number of bytes to log. 297 | func (t *undoTx) Log3(src unsafe.Pointer, size uintptr) error { 298 | uData := t.curr 299 | tail := t.tail 300 | 301 | exists := t.fs.insert(uintptr(src), size) 302 | if exists { 303 | return nil 304 | } 305 | 306 | srcU := uintptr(src) 307 | 308 | // Number of bytes we need to log this entry. It is size + uLogHdrSize, 309 | // rounded up to cache line size 310 | logSize := int((size+uLogHdrSize+63)/cacheSize) * cacheSize 311 | if tail+logSize > cap(uData.log) { 312 | t.increaseLogSize(logSize) 313 | uData = t.curr 314 | tail = t.tail 315 | } 316 | 317 | // Create a volatile copy of all pointers in this object. 318 | t.ptrArray = runtime.CollectPtrs(uintptr(src), int(size), t.ptrArray) 319 | 320 | tmpBuf := unsafe.Pointer(&t.tmpBuf[0]) 321 | diff := uintptr(tmpBuf) % cacheSize 322 | if diff != 0 { 323 | diff = cacheSize - diff 324 | tmpBuf = unsafe.Pointer(&t.tmpBuf[diff]) 325 | } 326 | 327 | // Append data to log entry 328 | // copy 64 bytes at a time 329 | 330 | genPtr := (*uintptr)(tmpBuf) 331 | szPtr := (*uintptr)(unsafe.Pointer(&t.tmpBuf[diff+8])) 332 | origPtr := (*uintptr)(unsafe.Pointer(&t.tmpBuf[diff+16])) 333 | dataPtr := unsafe.Pointer(&t.tmpBuf[diff+24]) 334 | 335 | *genPtr = t.genNum 336 | *szPtr = size 337 | *origPtr = uintptr(src) 338 | sz := uintptr(cacheSize - uLogHdrSize) 339 | if size < sz { 340 | sz = size 341 | } 342 | 343 | // copy the first sz bytes 344 | destSlc := (*(*[maxInt]byte)(dataPtr))[:sz] 345 | srcSlc := (*(*[maxInt]byte)(unsafe.Pointer(srcU)))[:sz] 346 | copy(destSlc, srcSlc) 347 | srcU += sz 348 | size -= sz 349 | movnt(unsafe.Pointer(&uData.log[tail]), tmpBuf, cacheSize) 350 | tail += cacheSize 351 | 352 | // Copy remaining 64 byte aligned entries 353 | for size >= cacheSize { 354 | destSlc = (*(*[maxInt]byte)(tmpBuf))[:] 355 | srcSlc = (*(*[maxInt]byte)(unsafe.Pointer(srcU)))[:cacheSize] 356 | copy(destSlc, srcSlc) 357 | movnt(unsafe.Pointer(&uData.log[tail]), tmpBuf, cacheSize) 358 | size -= cacheSize 359 | srcU += cacheSize 360 | tail += cacheSize 361 | } 362 | 363 | // Copy the final < 64-byte entry 364 | sz = size 365 | if sz > 0 { 366 | destSlc = (*(*[maxInt]byte)(tmpBuf))[:] 367 | srcSlc = (*(*[maxInt]byte)(unsafe.Pointer(srcU)))[:sz] 368 | copy(destSlc, srcSlc) 369 | movnt(unsafe.Pointer(&uData.log[tail]), tmpBuf, cacheSize) 370 | tail += cacheSize 371 | } 372 | 373 | runtime.Fence() // Fence after movnt 374 | t.tail = tail 375 | 376 | return nil 377 | } 378 | 379 | func (t *undoTx) Log2(src, dst unsafe.Pointer, size uintptr) error { 380 | log.Fatal("Log2() not implemented") 381 | return nil 382 | } 383 | 384 | // TODO: Logging slice of slice not supported 385 | func (t *undoTx) Log(intf ...interface{}) error { 386 | log.Fatal("Log() not implemented") 387 | doUpdate := false 388 | if len(intf) == 2 { // If method is invoked with syntax Log(ptr, data), 389 | // this method will do (*ptr = data) operation too 390 | doUpdate = true 391 | } else if len(intf) != 1 { 392 | return errors.New("[undoTx] Log: Incorrectly called. Correct usage: " + 393 | "Log(ptr, data) OR Log(ptr) OR Log(slice)") 394 | } 395 | v1 := reflect.ValueOf(intf[0]) 396 | if !runtime.InPmem(v1.Pointer()) { 397 | if doUpdate { 398 | updateVar(v1, reflect.ValueOf(intf[1])) 399 | } 400 | return errors.New("[undoTx] Log: Can't log data in volatile memory") 401 | } 402 | switch v1.Kind() { 403 | case reflect.Slice: 404 | t.logSlice(v1, true) 405 | case reflect.Ptr: 406 | /* TODO 407 | tail := t.tail 408 | oldv := reflect.Indirect(v1) // get the underlying data of pointer 409 | typ := oldv.Type() 410 | if oldv.Kind() == reflect.Slice { 411 | // record this log entry and store size of each slice element. 412 | // Used later during End() 413 | t.storeSliceHdr = append(t.storeSliceHdr, pair{tail, int(typ.Elem().Size())}) 414 | } 415 | 416 | size := int(typ.Size()) 417 | v2 := reflect.PNew(oldv.Type()) 418 | reflect.Indirect(v2).Set(oldv) // copy old data 419 | 420 | // Append data to log entry. 421 | uData := t.data 422 | uData.log[tail].ptr = unsafe.Pointer(v1.Pointer()) // point to orignal data 423 | uData.log[tail].data = unsafe.Pointer(v2.Pointer()) // point to logged copy 424 | uData.log[tail].genNum = 1 425 | uData.log[tail].size = size // size of data 426 | 427 | // Flush logged data copy and entry. 428 | runtime.FlushRange(uData.log[tail].data, uintptr(size)) 429 | runtime.FlushRange(unsafe.Pointer(&uData.log[tail]), 430 | unsafe.Sizeof(uData.log[tail])) 431 | 432 | // Update log offset in header. 433 | t.increaseLogSize() 434 | if oldv.Kind() == reflect.Slice { 435 | // Pointer to slice was passed to Log(). So, log slice elements too, 436 | // but no need to flush these contents in End() 437 | t.logSlice(oldv, false) 438 | } 439 | */ 440 | default: 441 | debug.PrintStack() 442 | return errors.New("[undoTx] Log: data must be pointer/slice") 443 | } 444 | if doUpdate { 445 | // Do the actual a = b operation here 446 | updateVar(v1, reflect.ValueOf(intf[1])) 447 | } 448 | return nil 449 | } 450 | 451 | func updateVar(ptr, data reflect.Value) { 452 | if ptr.Kind() != reflect.Ptr { 453 | panic(fmt.Sprintf("[undoTx] updateVar: Updating data pointed by"+ 454 | "ptr=%T not allowed", ptr)) 455 | } 456 | oldV := reflect.Indirect(ptr) 457 | if oldV.Kind() != reflect.Slice { // ptr.Kind() must be reflect.Ptr here 458 | if data.Kind() == reflect.Invalid { 459 | // data has value 460 | z := reflect.Zero(oldV.Type()) 461 | reflect.Indirect(ptr).Set(z) 462 | } else { 463 | reflect.Indirect(ptr).Set(data) 464 | } 465 | return 466 | } 467 | // must be slice header update 468 | if data.Kind() != reflect.Slice { 469 | panic(fmt.Sprintf("[undoTx] updateVar: Don't know how to log data type"+ 470 | "ptr=%T, data = %T", oldV, data)) 471 | } 472 | sourceVal := (*value)(unsafe.Pointer(&oldV)) 473 | sshdr := (*sliceHeader)(sourceVal.ptr) // source slice header 474 | vptr := (*value)(unsafe.Pointer(&data)) 475 | dshdr := (*sliceHeader)(vptr.ptr) // data slice header 476 | *sshdr = *dshdr 477 | } 478 | 479 | func (t *undoTx) logSlice(v1 reflect.Value, flushAtEnd bool) { 480 | // Don't create log entries, if there is no slice, or slice is not in pmem 481 | if v1.Pointer() == 0 || !runtime.InPmem(v1.Pointer()) { 482 | return 483 | } 484 | typ := v1.Type() 485 | v1len := v1.Len() 486 | size := v1len * int(typ.Elem().Size()) 487 | v2 := reflect.PMakeSlice(typ, v1len, v1len) 488 | vptr := (*value)(unsafe.Pointer(&v2)) 489 | vshdr := (*sliceHeader)(vptr.ptr) 490 | sourceVal := (*value)(unsafe.Pointer(&v1)) 491 | sshdr := (*sliceHeader)(sourceVal.ptr) 492 | if size != 0 { 493 | sourcePtr := (*[maxInt]byte)(sshdr.data)[:size:size] 494 | destPtr := (*[maxInt]byte)(vshdr.data)[:size:size] 495 | copy(destPtr, sourcePtr) 496 | } 497 | 498 | /* TODO 499 | tail := t.tail 500 | uData := t.data 501 | uData.log[tail].ptr = unsafe.Pointer(v1.Pointer()) // point to original data 502 | uData.log[tail].data = unsafe.Pointer(v2.Pointer()) // point to logged copy 503 | uData.log[tail].genNum = 1 504 | uData.log[tail].size = size // size of data 505 | 506 | if !flushAtEnd { 507 | // This happens when sliceheader is logged. User can then update 508 | // the sliceheader and/or the slice contents. So, when sliceheader is 509 | // flushed in End(), new slice contents also must be flushed in End(). 510 | // So this log entry containing old slice contents don't have to be 511 | // flushed in End(). Add this to list of entries to be skipped. 512 | t.skipList = append(t.skipList, tail) 513 | } 514 | 515 | // Flush logged data copy and entry. 516 | runtime.FlushRange(uData.log[tail].data, uintptr(size)) 517 | runtime.FlushRange(unsafe.Pointer(&uData.log[tail]), 518 | unsafe.Sizeof(uData.log[tail])) 519 | */ 520 | 521 | // Update log offset in header. 522 | t.increaseLogSize(0) // TODO 523 | } 524 | 525 | /* Exec function receives a variable number of interfaces as its arguments. 526 | * Usage: Exec(fn_name, fn_arg1, fn_arg2, ...) 527 | * Function fn_name() should not return anything. 528 | * No need to Begin() & End() transaction separately if Exec() is used. 529 | * Caveat: All locks within function fn_name(fn_arg1, fn_arg2, ...) should be 530 | * taken before making Exec() call. Locks should be released after Exec() call. 531 | */ 532 | func (t *undoTx) Exec(intf ...interface{}) (retVal []reflect.Value, err error) { 533 | if len(intf) < 1 { 534 | return retVal, 535 | errors.New("[undoTx] Exec: Must have atleast one argument") 536 | } 537 | fnPosInInterfaceArgs := 0 538 | fn := reflect.ValueOf(intf[fnPosInInterfaceArgs]) // The function to call 539 | if fn.Kind() != reflect.Func { 540 | return retVal, 541 | errors.New("[undoTx] Exec: 1st argument must be a function") 542 | } 543 | fnType := fn.Type() 544 | // Populate the arguments of the function correctly 545 | argv := make([]reflect.Value, fnType.NumIn()) 546 | if len(argv) != len(intf) { 547 | return retVal, errors.New("[undoTx] Exec: Incorrect no. of args in " + 548 | "function passed to Exec") 549 | } 550 | for i := range argv { 551 | if i == fnPosInInterfaceArgs { 552 | // Add t *undoTx as the 1st argument to be passed to the function 553 | // fn. This is not passed by the application when it calls Exec(). 554 | argv[i] = reflect.ValueOf(t) 555 | } else { 556 | // get the arguments to the function call from the call to Exec() 557 | // and populate in argv 558 | if reflect.TypeOf(intf[i]) != fnType.In(i) { 559 | return retVal, errors.New("[undoTx] Exec: Incorrect type of " + 560 | "args in function passed to Exec") 561 | } 562 | argv[i] = reflect.ValueOf(intf[i]) 563 | } 564 | } 565 | t.Begin() 566 | defer t.End() 567 | txLevel := t.level 568 | retVal = fn.Call(argv) 569 | if txLevel != t.level { 570 | return retVal, errors.New("[undoTx] Exec: Unbalanced Begin() & End() " + 571 | "calls inside function passed to Exec") 572 | } 573 | return retVal, err 574 | } 575 | 576 | func (t *undoTx) Begin() error { 577 | t.level++ 578 | return nil 579 | } 580 | 581 | /* Also persists the new data written by application, so application 582 | * doesn't need to do it separately. For nested transactions, End() call to 583 | * inner transaction does nothing. Only when the outermost transaction ends, 584 | * all application data is flushed to pmem. Returns a bool indicating if it 585 | * is safe to release the transaction handle. 586 | */ 587 | func (t *undoTx) End() bool { 588 | if t.level == 0 { 589 | return true 590 | } 591 | 592 | t.level-- 593 | if t.level == 0 { 594 | defer t.unLock() 595 | t.fs.flushAndDestroy() 596 | t.genNum++ 597 | t.first.genNum = t.genNum 598 | runtime.PersistRange(unsafe.Pointer(&t.first.genNum), 8) 599 | t.resetLogData() // discard all logs. 600 | return true 601 | } 602 | 603 | return false 604 | } 605 | 606 | func (t *undoTx) RLock(m *sync.RWMutex) { 607 | m.RLock() 608 | t.rlocks = append(t.rlocks, m) 609 | } 610 | 611 | func (t *undoTx) WLock(m *sync.RWMutex) { 612 | m.Lock() 613 | t.wlocks = append(t.wlocks, m) 614 | } 615 | 616 | func (t *undoTx) Lock(m *sync.RWMutex) { 617 | t.WLock(m) 618 | } 619 | 620 | func (t *undoTx) unLock() { 621 | for i, m := range t.wlocks { 622 | m.Unlock() 623 | t.wlocks[i] = nil 624 | } 625 | t.wlocks = t.wlocks[:0] 626 | 627 | for i, m := range t.rlocks { 628 | m.RUnlock() 629 | t.rlocks[i] = nil 630 | } 631 | t.rlocks = t.rlocks[:0] 632 | } 633 | 634 | // abort() aborts the ongoing undo transaction. It reverts the changes made by 635 | // the application by copying the logged copy of the data to its original 636 | // location in persistent memory. swizzle indicates if pointers has to be 637 | // swizzled before being dereferenced. 638 | func (t *undoTx) abort(swizzle bool) error { 639 | defer t.unLock() 640 | t.level = 0 641 | uData := t.first 642 | 643 | // Aborting log entries has to be done from last to first. Since this is 644 | // difficult when using a linked list of array, undoEntries first builds 645 | // a list of pointers at which each entry begins. These are then aborted in 646 | // the inverse order in the next step 647 | var undoEntries []uintptr 648 | 649 | for uData != nil { 650 | off := 0 651 | for off+64 <= len(uData.log) { 652 | var logBuf unsafe.Pointer 653 | if swizzle { 654 | shdr := (*sliceHeader)(unsafe.Pointer(&uData.log)) 655 | sPtr := shdr.data 656 | sPtrSwizz := runtime.SwizzlePointer(uintptr(sPtr)) 657 | logBuf = unsafe.Pointer(uintptr(sPtrSwizz) + uintptr(off)) 658 | } else { 659 | logBuf = unsafe.Pointer(&uData.log[off]) 660 | } 661 | genPtr := (*uintptr)(logBuf) 662 | if *genPtr != t.genNum { 663 | // The generation number of this entry does not match the 664 | // current active generation number. So this is not a valid 665 | // log entry. 666 | break 667 | } 668 | 669 | sizePtr := (unsafe.Pointer(uintptr(logBuf) + ptrSize)) 670 | size := *(*uintptr)(sizePtr) 671 | undoEntries = append(undoEntries, uintptr(sizePtr)) 672 | toAdd := int((size+uLogHdrSize+63)/64) * 64 673 | off += toAdd 674 | } 675 | uData = uData.next 676 | if swizzle { 677 | uDatap := uintptr(unsafe.Pointer(uData)) 678 | uData = (*uLogData)(unsafe.Pointer(runtime.SwizzlePointer(uDatap))) 679 | } 680 | } 681 | 682 | for j := len(undoEntries) - 1; j >= 0; j-- { 683 | entry := undoEntries[j] 684 | size := *(*uintptr)(unsafe.Pointer(entry)) 685 | origPtr := *(*uintptr)(unsafe.Pointer(entry + ptrSize)) 686 | if swizzle { 687 | origPtr = runtime.SwizzlePointer(origPtr) 688 | } 689 | dataPtr := unsafe.Pointer(entry + 16) 690 | origData := (*[maxInt]byte)(unsafe.Pointer(origPtr)) 691 | logData := (*[maxInt]byte)(dataPtr) 692 | copy(origData[:size], logData[:]) 693 | runtime.FlushRange(unsafe.Pointer(origPtr), size) 694 | } 695 | runtime.Fence() 696 | 697 | t.first.genNum++ 698 | runtime.PersistRange(unsafe.Pointer(&t.first.genNum), ptrSize) 699 | t.genNum = t.first.genNum 700 | t.resetLogData() 701 | return nil 702 | } 703 | 704 | // If runtime needs to do pointer swizzling duing initilization, then undo log 705 | // entries need to be reverted before doing pointer swizzling. This function is 706 | // registered as a callback with the runtime, and will be called before pointers 707 | // are swizzled. rootPtr is the swizzled root pointer set by the application. 708 | func SwizzleAndAbort(rootPtr unsafe.Pointer) { 709 | // pmemHeader definition is not available here. 710 | type pmh struct { 711 | undoTxHeadPtr unsafe.Pointer 712 | redoTxHeadPtr unsafe.Pointer 713 | appData []int // namedObject definition not available here 714 | } 715 | appRootPtr := (*pmh)(rootPtr) 716 | undoTxSwizzled := runtime.SwizzlePointer(uintptr(unsafe.Pointer(appRootPtr.undoTxHeadPtr))) 717 | undoTxHeadPtr := (*undoTxHeader)(unsafe.Pointer(undoTxSwizzled)) 718 | 719 | // Check if the magic number matches 720 | if undoTxHeadPtr.magic != magic { 721 | log.Fatal("undoTxHeader magic does not match!") 722 | } 723 | 724 | for i := 0; i < logNum; i++ { 725 | handle := &undoTxHeadPtr.logData[i] 726 | uHandles[i].first = handle 727 | uHandles[i].genNum = handle.genNum 728 | // Reallocate the array for the log entries. TODO: How does this 729 | // change with tail not in pmem? 730 | uHandles[i].abort(true) 731 | } 732 | 733 | } 734 | -------------------------------------------------------------------------------- /txtest/crashtest/txCrash_test.go: -------------------------------------------------------------------------------- 1 | // +build crash 2 | 3 | /////////////////////////////////////////////////////////////////////// 4 | // Copyright 2018-2019 VMware, Inc. 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | /////////////////////////////////////////////////////////////////////// 7 | 8 | // This test needs to be run twice to check for data consistency after crash. 9 | // Hence this test is not run by default and will only be run if a flag 10 | // 'crash' is specified while running the tests. 11 | // 12 | // E.g.: ~/go-pmem/bin/go test -tags="crash" -v # run 1 13 | // E.g.: ~/go-pmem/bin/go test -tags="crash" -v # run 2 14 | 15 | package crashtest 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "testing" 21 | "unsafe" 22 | 23 | "github.com/vmware/go-pmem-transaction/pmem" 24 | "github.com/vmware/go-pmem-transaction/transaction" 25 | ) 26 | 27 | type st struct { 28 | slice []int 29 | } 30 | 31 | const ( 32 | sliceHdrSize = uintptr(24) 33 | ) 34 | 35 | // This test makes sure that undo transaction doesn't store any contents of a 36 | // slice in volatile memory. If it does, on the restart-after-crash path, GC 37 | // will zero out the volatile pointers stored in the log entries, and abort will 38 | // segfault. 39 | func TestUpdateCrash(t *testing.T) { 40 | firstInit := pmem.Init("testfile") 41 | var st1 *st 42 | if firstInit { 43 | st1 = (*st)(pmem.New("r1", st1)) 44 | tx := transaction.NewUndoTx() 45 | tx.Begin() 46 | tx.Log3(unsafe.Pointer(&st1.slice), sliceHdrSize) 47 | //slicehdr is in pmem, but create slice contents in volatile memory 48 | st1.slice = make([]int, 3) 49 | tx.End() 50 | 51 | st2 := (*st)(pmem.Get("r1", st1)) 52 | tx.Begin() 53 | tx.Log3(unsafe.Pointer(&st2.slice), sliceHdrSize) 54 | st2.slice = []int{10, 20, 30, 40} 55 | fmt.Println("[TestUpdateCrash]: Crashing now") 56 | return // <-- return before ending transaction to simulate CRASH! 57 | } else { 58 | fmt.Println("Testing abort when logged slice is in volatile memory") 59 | st2 := (*st)(pmem.Get("r1", st1)) 60 | if len(st2.slice) != 3 { 61 | t.Errorf("want = %d, got = %d", 3, len(st2.slice)) 62 | } 63 | if st2.slice != nil { 64 | t.Errorf("want = nil, got = %v", st2.slice) 65 | } 66 | fmt.Println("[TestUpdateCrash] successful") 67 | os.Remove("testfile") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /txtest/log3SwizzleTest/txLog3Swizzle_test.go: -------------------------------------------------------------------------------- 1 | // +build swizzleTest 2 | 3 | /////////////////////////////////////////////////////////////////////// 4 | // Copyright 2018-2019 VMware, Inc. 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | /////////////////////////////////////////////////////////////////////// 7 | 8 | // This test requires to be run twice to check undo log abort works correctly 9 | // when pointers are swizzled in the recovery path. 10 | 11 | // Hence this test is not run by default and will only be run if a flag 12 | // 'swizzleTest' is specified while running the tests. 13 | // 14 | // E.g.: ~/go-pmem/bin/go test -tags="swizzleTest" -v # run 1 15 | // E.g.: ~/go-pmem/bin/go test -tags="swizzleTest" -v # run 2 16 | 17 | package log3SwizzleTest 18 | 19 | import ( 20 | "fmt" 21 | "log" 22 | "runtime" 23 | "runtime/debug" 24 | "testing" 25 | "unsafe" 26 | 27 | "github.com/vmware/go-pmem-transaction/pmem" 28 | "github.com/vmware/go-pmem-transaction/transaction" 29 | ) 30 | 31 | const ( 32 | intSize = 8 // Size of an int variable 33 | loopSize = 8192 34 | ) 35 | 36 | type logData struct { 37 | i int 38 | ip *int 39 | } 40 | 41 | type log3Swizzle struct { 42 | int1 []logData 43 | int2 []logData 44 | int3 []logData 45 | } 46 | 47 | // Ensure that runtime pointer swizzling is force enabled before running the 48 | // test 49 | func TestUndoLog3Swizzle(t *testing.T) { 50 | firstInit := pmem.Init("/mnt/ext4-pmem0/log3swizzle") 51 | 52 | var log3Struct *log3Swizzle 53 | if firstInit { 54 | log3Struct = (*log3Swizzle)(pmem.New("log3Swizzle", log3Struct)) 55 | // Create three int arrays occupying 60MB each. This ensures that three 56 | // separate persistent memory arenas will be created. 57 | log3Struct.int1 = pmake([]logData, 60*1024*1024/16) // 60MB 58 | log3Struct.int2 = pmake([]logData, 60*1024*1024/16) // 60MB 59 | log3Struct.int3 = pmake([]logData, 60*1024*1024/16) // 60MB 60 | runtime.PersistRange(unsafe.Pointer(log3Struct), unsafe.Sizeof(*log3Struct)) 61 | 62 | // Write 0xFFFFFF data to the first three entries of int1, int2, int3. 63 | // TODO later change the bounds so that it will overflow one linked log3 array 64 | for i := 0; i < loopSize; i++ { 65 | log3Struct.int1[i].i = 0xAAAAAA 66 | log3Struct.int1[i].ip = &log3Struct.int2[i].i 67 | 68 | log3Struct.int2[i].i = 0xBBBBBB 69 | log3Struct.int2[i].ip = &log3Struct.int3[i].i 70 | 71 | log3Struct.int3[i].i = 0xCCCCCC 72 | log3Struct.int3[i].ip = &log3Struct.int1[i].i 73 | } 74 | 75 | runtime.PersistRange(unsafe.Pointer(&log3Struct.int1[0]), loopSize*intSize*2) 76 | runtime.PersistRange(unsafe.Pointer(&log3Struct.int2[0]), loopSize*intSize*2) 77 | runtime.PersistRange(unsafe.Pointer(&log3Struct.int3[0]), loopSize*intSize*2) 78 | 79 | undoTx := transaction.NewUndoTx() 80 | undoTx.Begin() 81 | for i := 0; i < loopSize; i++ { 82 | undoTx.Log3(unsafe.Pointer(&log3Struct.int1[i].i), intSize) 83 | log3Struct.int1[i].i = 0xABCDEF 84 | undoTx.Log3(unsafe.Pointer(&log3Struct.int2[i].i), intSize) 85 | log3Struct.int2[i].i = 0xBCEDFA 86 | undoTx.Log3(unsafe.Pointer(&log3Struct.int3[i].i), intSize) 87 | log3Struct.int3[i].i = 0xCDEFAB 88 | 89 | undoTx.Log3(unsafe.Pointer(&log3Struct.int1[i].ip), intSize) 90 | log3Struct.int1[i].ip = &log3Struct.int3[i].i 91 | undoTx.Log3(unsafe.Pointer(&log3Struct.int2[i].ip), intSize) 92 | log3Struct.int2[i].ip = &log3Struct.int1[i].i 93 | undoTx.Log3(unsafe.Pointer(&log3Struct.int3[i].ip), intSize) 94 | log3Struct.int3[i].ip = &log3Struct.int2[i].i 95 | } 96 | 97 | // Induce an application crash here 98 | return // <-- return before ending transaction to simulate CRASH! 99 | 100 | } else { 101 | log3Struct = (*log3Swizzle)(pmem.Get("log3Swizzle", log3Struct)) 102 | if log3Struct == nil { 103 | log.Fatal("Invalid application state") 104 | } 105 | 106 | for i := 0; i < loopSize; i++ { 107 | assertEqual(t, log3Struct.int1[i].i, 0xAAAAAA) 108 | assertEqual(t, log3Struct.int2[i].i, 0xBBBBBB) 109 | assertEqual(t, log3Struct.int3[i].i, 0xCCCCCC) 110 | 111 | assertEqual(t, *log3Struct.int1[i].ip, log3Struct.int2[i].i) 112 | assertEqual(t, *log3Struct.int2[i].ip, log3Struct.int3[i].i) 113 | assertEqual(t, *log3Struct.int3[i].ip, log3Struct.int1[i].i) 114 | } 115 | } 116 | } 117 | 118 | func assertEqual(t *testing.T, a interface{}, b interface{}) { 119 | if a != b { 120 | debug.PrintStack() 121 | t.Fatal(fmt.Sprintf("%v != %v", a, b)) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /txtest/txExec_test.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | package txtest 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "reflect" 12 | "runtime/debug" 13 | "testing" 14 | "unsafe" 15 | 16 | "github.com/vmware/go-pmem-transaction/transaction" 17 | ) 18 | 19 | const ( 20 | intSize = 8 21 | ) 22 | 23 | type basic struct { 24 | i int 25 | slice []int 26 | } 27 | 28 | func add(tx transaction.TX, a *basic, b *basic) { 29 | tx.Log3(unsafe.Pointer(&a.i), intSize) 30 | a.i = a.i + b.i 31 | } 32 | 33 | func addRet(tx transaction.TX, a *basic, b *basic) int { 34 | tx.Log3(unsafe.Pointer(&a.i), intSize) 35 | return a.i + b.i 36 | } 37 | 38 | func TestExec(t *testing.T) { 39 | // Set up pmem appRoot and header 40 | 41 | var ( 42 | tx transaction.TX 43 | err error 44 | retVal []reflect.Value 45 | ) 46 | 47 | a := pnew(basic) 48 | b := pnew(basic) 49 | a.slice = pmake([]int, 0) 50 | b.slice = pmake([]int, 0) 51 | 52 | // Enumerating possible errors Exec() can return 53 | errNoArgs := errors.New("[undoTx] Exec: Must have atleast one argument") 54 | errFirstArg := errors.New("[undoTx] Exec: 1st argument must be a function") 55 | errNumArgs := errors.New("[undoTx] Exec: Incorrect no. of args in " + 56 | "function passed to Exec") 57 | errTypeArgs := errors.New("[undoTx] Exec: Incorrect type of args in " + 58 | "function passed to Exec") 59 | errTxBeginEnd := errors.New("[undoTx] Exec: Unbalanced Begin() & End() " + 60 | "calls inside function passed to Exec") 61 | errNil := err 62 | 63 | fmt.Println("Testing with no arguments") 64 | tx = transaction.NewUndoTx() 65 | _, err = tx.Exec() 66 | transaction.Release(tx) 67 | assertEqual(t, err.Error(), errNoArgs.Error()) 68 | 69 | fmt.Println("Testing when 1st argument is incorrect") 70 | tx = transaction.NewUndoTx() 71 | _, err = tx.Exec(2, 3, add) 72 | transaction.Release(tx) 73 | assertEqual(t, err.Error(), errFirstArg.Error()) 74 | 75 | fmt.Println("Testing when number of arguments don't match") 76 | tx = transaction.NewUndoTx() 77 | _, err = tx.Exec(add, 2) 78 | transaction.Release(tx) 79 | assertEqual(t, err.Error(), errNumArgs.Error()) 80 | 81 | fmt.Println("Testing when type of arguments don't match") 82 | tx = transaction.NewUndoTx() 83 | _, err = tx.Exec(add, 2, 3) 84 | transaction.Release(tx) 85 | assertEqual(t, err.Error(), errTypeArgs.Error()) 86 | 87 | fmt.Println("Testing with more Begin() than End() calls inside function " + 88 | "passed to Exec") 89 | a.i = 2 90 | b.i = 3 91 | tx = transaction.NewUndoTx() 92 | _, err = tx.Exec(func(tx transaction.TX) { 93 | tx.Begin() 94 | tx.Log3(unsafe.Pointer(a), unsafe.Sizeof(*a)) 95 | a.i = a.i + b.i 96 | }) 97 | transaction.Release(tx) 98 | assertEqual(t, a.i, 2) // TX reverted as Release() is called without End() 99 | assertEqual(t, err.Error(), errTxBeginEnd.Error()) 100 | 101 | fmt.Println("Testing with Begin() & End() calls inside function " + 102 | "passed to Exec") 103 | a.i = 20 104 | b.i = 30 105 | tx = transaction.NewUndoTx() 106 | _, err = tx.Exec(func(tx transaction.TX) { 107 | tx.Begin() 108 | tx.Log3(unsafe.Pointer(a), unsafe.Sizeof(*a)) 109 | a.i = a.i + b.i 110 | tx.End() 111 | }) 112 | transaction.Release(tx) 113 | assertEqual(t, a.i, 50) 114 | assertEqual(t, err, errNil) 115 | 116 | fmt.Println("Testing when End() is called inside function passed to Exec") 117 | a.i = 200 118 | b.i = 300 119 | tx = transaction.NewUndoTx() 120 | _, err = tx.Exec(func(tx transaction.TX) { 121 | tx.Log3(unsafe.Pointer(a), unsafe.Sizeof(*a)) 122 | a.i = a.i + b.i 123 | tx.End() 124 | }) 125 | transaction.Release(tx) 126 | assertEqual(t, a.i, 500) // TX completed successfully but returns error 127 | assertEqual(t, err.Error(), errTxBeginEnd.Error()) 128 | 129 | fmt.Println("Testing simple add function") 130 | a.i = 2 131 | b.i = 3 132 | tx = transaction.NewUndoTx() 133 | retVal, err = tx.Exec(add, a, b) 134 | transaction.Release(tx) 135 | assertEqual(t, err, errNil) 136 | assertEqual(t, a.i, 5) 137 | assertEqual(t, len(retVal), 0) 138 | 139 | fmt.Println("Testing with anon function & closure") 140 | a.i = 20 141 | b.i = 30 142 | tx = transaction.NewUndoTx() 143 | retVal, err = tx.Exec(func(tx transaction.TX) { 144 | tx.Log3(unsafe.Pointer(a), unsafe.Sizeof(*a)) 145 | a.i = a.i + b.i 146 | a.slice = append(a.slice, 40) 147 | a.slice = append(a.slice, 50) 148 | }) 149 | transaction.Release(tx) 150 | assertEqual(t, err, errNil) 151 | assertEqual(t, a.i, 50) 152 | assertEqual(t, len(a.slice), 2) 153 | assertEqual(t, a.slice[0], 40) 154 | assertEqual(t, a.slice[1], 50) 155 | assertEqual(t, len(retVal), 0) 156 | 157 | fmt.Println("Testing with abort before tx.End()") 158 | a.i = 200 159 | b.i = 300 160 | a.slice = pmake([]int, 0) 161 | tx = transaction.NewUndoTx() 162 | _, err = tx.Exec(func(tx transaction.TX) { 163 | tx.Log3(unsafe.Pointer(a), unsafe.Sizeof(*a)) 164 | a.i = a.i + b.i 165 | a.slice = append(a.slice, 400) 166 | 167 | // Release aborts the transaction because tx.End() is not called yet 168 | transaction.Release(tx) 169 | }) 170 | assertEqual(t, err.Error(), errTxBeginEnd.Error()) 171 | assertEqual(t, a.i, 200) 172 | assertEqual(t, len(a.slice), 0) // Changes rolled back successfully. 173 | 174 | fmt.Printf("Testing when some changes inside function passed to tx.Exec()") 175 | fmt.Println(" are not logged & there is abort before tx.End()") 176 | a.i = 2000 177 | b.i = 3000 178 | tx = transaction.NewUndoTx() 179 | _, err = tx.Exec(func(tx transaction.TX) { 180 | a.i = a.i + b.i 181 | tx.Log3(unsafe.Pointer(&a.slice), 24) // 24 is the size of slice header 182 | a.slice = append(a.slice, 4000) 183 | 184 | // Release aborts the transaction because tx.End() is not called yet 185 | transaction.Release(tx) 186 | }) 187 | assertEqual(t, err.Error(), errTxBeginEnd.Error()) 188 | assertEqual(t, a.i, 5000) 189 | assertEqual(t, len(a.slice), 0) 190 | 191 | fmt.Println("Testing simple add function with return value") 192 | a.i = 3 193 | b.i = 6 194 | tx = transaction.NewUndoTx() 195 | retVal, err = tx.Exec(addRet, a, b) 196 | transaction.Release(tx) 197 | assertEqual(t, err, errNil) 198 | assertEqual(t, len(retVal), 1) 199 | assertEqual(t, (int)(retVal[0].Int()), 9) 200 | 201 | fmt.Println("Testing anon function with struct ptr return value") 202 | a.i = 30 203 | b.i = 60 204 | tx = transaction.NewUndoTx() 205 | retVal, err = tx.Exec(func(tx transaction.TX) *basic { 206 | c := pnew(basic) 207 | c.slice = pmake([]int, 0) 208 | tx.Log3(unsafe.Pointer(c), unsafe.Sizeof(*c)) 209 | c.i = a.i + b.i 210 | c.slice = append(c.slice, 40) 211 | c.slice = append(c.slice, 50) 212 | return c 213 | }) 214 | transaction.Release(tx) 215 | assertEqual(t, err, errNil) 216 | assertEqual(t, len(retVal), 1) 217 | retPtrVal := retVal[0].Elem() 218 | retSum := (int)(retPtrVal.Field(0).Int()) 219 | retSlice := retPtrVal.Field(1).Slice(0, 2) 220 | assertEqual(t, retSum, 90) 221 | assertEqual(t, (int)(retSlice.Index(0).Int()), 40) 222 | assertEqual(t, (int)(retSlice.Index(1).Int()), 50) 223 | } 224 | 225 | func assertEqual(t *testing.T, a interface{}, b interface{}) { 226 | if a != b { 227 | debug.PrintStack() 228 | t.Fatal(fmt.Sprintf("%v != %v", a, b)) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /txtest/txLock_test.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | package txtest 7 | 8 | import ( 9 | "fmt" 10 | "sync" 11 | "testing" 12 | "time" 13 | 14 | "github.com/vmware/go-pmem-transaction/transaction" 15 | ) 16 | 17 | func TestUndoLogLock(t *testing.T) { 18 | fmt.Println("Testing UndoTx locking") 19 | m := new(sync.RWMutex) 20 | tx := transaction.NewUndoTx() 21 | tx.Begin() 22 | tx.Lock(m) 23 | fmt.Println("Crash now & restart UndoLogLock to test no locks held" + 24 | "after crash") 25 | time.Sleep(5 * time.Second) 26 | transaction.Release(tx) 27 | } 28 | 29 | func TestRedoLogLock(t *testing.T) { 30 | fmt.Println("Testing RedoTx locking") 31 | m := new(sync.RWMutex) 32 | tx := transaction.NewRedoTx() 33 | tx.Begin() 34 | tx.Lock(m) 35 | fmt.Println("Crash now & restart RedoLogLock to see if no locks held" + 36 | "after crash") 37 | time.Sleep(5 * time.Second) 38 | transaction.Release(tx) 39 | } 40 | -------------------------------------------------------------------------------- /txtest/txLog3_test.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | package txtest 7 | 8 | import ( 9 | "fmt" 10 | "testing" 11 | "unsafe" 12 | 13 | "github.com/vmware/go-pmem-transaction/transaction" 14 | ) 15 | 16 | // Common data types and functions used across tests 17 | 18 | type structLogTest struct { 19 | i int 20 | iptr *int 21 | slice []int 22 | } 23 | 24 | var ( 25 | j *int 26 | b *bool 27 | slice1 []int 28 | slice2 []int 29 | struct1 *structLogTest 30 | struct2 *structLogTest 31 | ) 32 | 33 | func resetData() { 34 | b = pnew(bool) 35 | j = pnew(int) 36 | slice1 = pmake([]int, 100, 100) 37 | slice2 = pmake([]int, 100, 100) 38 | struct1 = pnew(structLogTest) 39 | struct2 = pnew(structLogTest) 40 | struct1.i = 1 41 | struct2.i = 2 42 | } 43 | 44 | // Keeping Log3 tests in a separate file 45 | 46 | func TestUndoLog3Basic(t *testing.T) { 47 | resetData() 48 | undoTx := transaction.NewUndoTx() 49 | fmt.Println("Testing basic data type commit.") 50 | undoTx.Begin() 51 | undoTx.Log3(unsafe.Pointer(b), 1) 52 | undoTx.Log3(unsafe.Pointer(j), 8) 53 | undoTx.Log3(unsafe.Pointer(&struct1.i), 8) 54 | *b = true 55 | *j = 10 56 | struct1.i = 100 57 | undoTx.End() 58 | assertEqual(t, *b, true) 59 | assertEqual(t, *j, 10) 60 | assertEqual(t, struct1.i, 100) 61 | 62 | fmt.Println("Testing basic data type abort.") 63 | undoTx.Begin() 64 | undoTx.Log3(unsafe.Pointer(b), 1) 65 | undoTx.Log3(unsafe.Pointer(j), 8) 66 | *b = false 67 | *j = 0 68 | transaction.Release(undoTx) // Calls abort internally 69 | assertEqual(t, *b, true) 70 | assertEqual(t, *j, 10) 71 | 72 | fmt.Println("Testing basic data type abort after multiple updates.") 73 | undoTx = transaction.NewUndoTx() 74 | undoTx.Begin() 75 | undoTx.Log3(unsafe.Pointer(j), 8) 76 | *j = 100 77 | *j = 1000 78 | *j = 10000 79 | transaction.Release(undoTx) // Calls abort internally 80 | assertEqual(t, *j, 10) 81 | 82 | fmt.Println("Testing data structure commit.") 83 | undoTx = transaction.NewUndoTx() 84 | undoTx.Begin() 85 | undoTx.Log3(unsafe.Pointer(struct1), unsafe.Sizeof(*struct1)) 86 | struct1.i = 10 87 | struct1.iptr = &struct1.i 88 | struct1.slice = slice1 89 | undoTx.Log3(unsafe.Pointer(&slice1[0]), 8) 90 | slice1[0] = 11 91 | undoTx.End() 92 | assertEqual(t, struct1.i, 10) 93 | assertEqual(t, *struct1.iptr, 10) 94 | assertEqual(t, struct1.slice[0], slice1[0]) 95 | 96 | undoTx.Begin() 97 | undoTx.Log3(unsafe.Pointer(struct2), unsafe.Sizeof(*struct2)) 98 | struct2.slice = slice2 99 | undoTx.Log3(unsafe.Pointer(struct1), unsafe.Sizeof(*struct1)) 100 | *struct1 = *struct2 101 | struct1.iptr = &struct1.i 102 | undoTx.Log3(unsafe.Pointer(&slice2[0]), 8) 103 | slice2[0] = 22 104 | undoTx.End() 105 | assertEqual(t, struct1.i, struct2.i) 106 | assertEqual(t, *struct1.iptr, struct2.i) 107 | assertEqual(t, struct1.slice[0], slice2[0]) 108 | 109 | fmt.Println("Testing data structure abort.") 110 | undoTx.Begin() 111 | undoTx.Log3(unsafe.Pointer(struct1), unsafe.Sizeof(*struct1)) 112 | struct1.i = 10 113 | struct1.iptr = nil 114 | undoTx.Log3(unsafe.Pointer(&struct1.slice), 24) 115 | struct1.slice = slice1 116 | transaction.Release(undoTx) 117 | assertEqual(t, struct1.i, 2) 118 | assertEqual(t, struct1.iptr, &struct1.i) 119 | assertEqual(t, struct1.slice[0], slice2[0]) 120 | 121 | undoTx = transaction.NewUndoTx() 122 | undoTx.Begin() 123 | undoTx.Log3(unsafe.Pointer(struct1.iptr), 8) 124 | struct1.i = 100 125 | transaction.Release(undoTx) 126 | assertEqual(t, struct1.i, 2) 127 | 128 | undoTx = transaction.NewUndoTx() 129 | undoTx.Begin() 130 | struct1.iptr = j 131 | undoTx.End() 132 | undoTx.Begin() 133 | undoTx.Log3(unsafe.Pointer(struct1), unsafe.Sizeof(*struct1)) 134 | *(struct1.iptr) = 1000 // redirect update will not rollback 135 | undoTx.Log3(unsafe.Pointer(struct2), unsafe.Sizeof(*struct2)) 136 | struct2.i = 3 137 | struct2.iptr = &struct2.i 138 | struct2.slice = slice1 139 | *struct1 = *struct2 140 | transaction.Release(undoTx) 141 | assertEqual(t, struct1.i, 2) 142 | assertEqual(t, *struct1.iptr, 1000) 143 | assertEqual(t, struct1.slice[0], slice2[0]) 144 | 145 | fmt.Println("Testing abort when nil slice is logged") 146 | struct1 = pnew(structLogTest) 147 | undoTx = transaction.NewUndoTx() 148 | undoTx.Begin() 149 | undoTx.Log3(unsafe.Pointer(&struct1.slice), unsafe.Sizeof(struct1.slice)) 150 | struct1.slice = pmake([]int, 2) 151 | transaction.Release(undoTx) // <-- abort 152 | if struct1.slice != nil { 153 | assertEqual(t, 0, 1) // Assert 154 | } 155 | } 156 | 157 | func TestUndoLog3Expand(t *testing.T) { 158 | sizeToCheck := 65536 159 | undoTx := transaction.NewUndoTx() 160 | 161 | fmt.Println("Testing undo log expansion commit by logging more entries") 162 | 163 | slice1 = pmake([]int, sizeToCheck) 164 | undoTx.Begin() 165 | for i := 0; i < sizeToCheck; i++ { 166 | undoTx.Log3(unsafe.Pointer(&slice1[i]), 8) 167 | slice1[i] = i 168 | } 169 | undoTx.End() 170 | transaction.Release(undoTx) 171 | for i := 0; i < sizeToCheck; i++ { 172 | assertEqual(t, slice1[i], i) 173 | } 174 | 175 | fmt.Println("Testing undo log expansion abort") 176 | slice1 = pmake([]int, sizeToCheck) 177 | for i := 0; i < sizeToCheck; i++ { 178 | assertEqual(t, slice1[i], 0) 179 | } 180 | sizeToAbort := 65500 181 | undoTx = transaction.NewUndoTx() 182 | undoTx.Begin() 183 | for i := 0; i < sizeToCheck; i++ { 184 | undoTx.Log3(unsafe.Pointer(&slice1[i]), 8) 185 | slice1[i] = i 186 | if i == sizeToAbort { 187 | break 188 | } 189 | } 190 | transaction.Release(undoTx) 191 | for i := 0; i < sizeToCheck; i++ { 192 | assertEqual(t, slice1[i], 0) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /txtest/txRedoLog_test.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | package txtest 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "sync" 12 | "testing" 13 | "unsafe" 14 | 15 | "github.com/vmware/go-pmem-transaction/transaction" 16 | ) 17 | 18 | func BenchmarkRedoLogInt(b *testing.B) { 19 | j = pnew(int) 20 | tx := transaction.NewRedoTx() 21 | b.ResetTimer() 22 | for i := 0; i < b.N; i++ { 23 | tx.Begin() 24 | tx.Log(j, i) 25 | tx.End() 26 | } 27 | transaction.Release(tx) 28 | } 29 | 30 | func BenchmarkRedoLog100Ints(b *testing.B) { 31 | slice1 = pmake([]int, 100) 32 | tx := transaction.NewUndoTx() 33 | b.ResetTimer() 34 | for i := 0; i < b.N; i++ { 35 | tx.Begin() 36 | for c := 0; c < 100; c++ { 37 | tx.Log(&slice1[c], i) 38 | } 39 | tx.End() 40 | } 41 | transaction.Release(tx) 42 | } 43 | 44 | func BenchmarkRedoLogSlice(b *testing.B) { 45 | struct1 = pnew(structLogTest) 46 | struct2 = pnew(structLogTest) 47 | struct1.slice = pmake([]int, 10000) 48 | struct2.slice = pmake([]int, 10000) 49 | tx := transaction.NewRedoTx() 50 | b.ResetTimer() 51 | for i := 0; i < b.N; i++ { 52 | tx.Begin() 53 | struct2.slice[i%1000] = i 54 | tx.Log(struct1.slice, struct2.slice) 55 | tx.End() 56 | } 57 | transaction.Release(tx) 58 | } 59 | 60 | func BenchmarkRedoLogReadInt(b *testing.B) { 61 | j = pnew(int) 62 | tx := transaction.NewRedoTx() 63 | tx.Begin() 64 | tx.Log(j, b.N) 65 | b.ResetTimer() 66 | for i := 0; i < b.N; i++ { 67 | a := tx.ReadLog(j) 68 | _ = a 69 | } 70 | tx.End() 71 | transaction.Release(tx) 72 | } 73 | 74 | func TestRedoLogRead(t *testing.T) { 75 | resetData() 76 | tx := transaction.NewRedoTx() 77 | 78 | struct1.slice = pmake([]int, 10) 79 | struct2.slice = pmake([]int, 100) 80 | struct1.slice[0] = 9 81 | struct2.slice[0] = 2 82 | struct2.slice[20] = 1 83 | 84 | fmt.Println("Testing TX.Readlog with 1 arg (ptr)") 85 | tx.Begin() 86 | // struct1.i = struct2.slice[0]+3 87 | tx.Log(&struct1.i, tx.ReadLog(&struct2.slice[0]).(int)+3) 88 | tx.End() 89 | assertEqual(t, struct1.i, 5) 90 | 91 | fmt.Println("Testing TX.Readlog with 2 args (read slice element)") 92 | tx.Begin() 93 | // struct1.slice = struct2.slice 94 | tx.Log(&struct1.slice, struct2.slice) 95 | // *j = struct1.slice[20] + 2 96 | tx.Log(j, tx.ReadLog(&struct1.slice, 20).(int)+2) 97 | tx.End() 98 | assertEqual(t, *j, 3) 99 | assertEqual(t, len(struct1.slice), 100) 100 | assertEqual(t, struct1.slice[0], 2) 101 | 102 | fmt.Println("Testing TX.Readlog with 3 args (read slice)") 103 | struct2.slice[90] = 90 104 | tx.Begin() 105 | // struct1.slice = struct2.slice[88:92] 106 | tx.Log(&struct1.slice, tx.ReadLog(&struct2.slice, 88, 92).([]int)) 107 | // *j = struct1.slice[2] 108 | tx.Log(j, tx.ReadLog(&struct1.slice, 2).(int)+1) 109 | tx.End() 110 | transaction.Release(tx) 111 | assertEqual(t, len(struct1.slice), 4) 112 | assertEqual(t, struct1.slice[2], 90) 113 | assertEqual(t, *j, 91) 114 | } 115 | 116 | func TestRedoLogExpand(t *testing.T) { 117 | fmt.Println("Testing redo log expansion commit by logging more entries") 118 | redoTx := transaction.NewRedoTx() 119 | sizeToCheck := transaction.NumEntries*4 + 1 120 | slice1 = pmake([]int, sizeToCheck) 121 | redoTx.Begin() 122 | for i := 0; i < sizeToCheck; i++ { 123 | redoTx.Log(&slice1[i], i) 124 | } 125 | redoTx.End() 126 | transaction.Release(redoTx) 127 | for i := 0; i < sizeToCheck; i++ { 128 | assertEqual(t, slice1[i], i) 129 | } 130 | 131 | fmt.Println("Testing redo log expansion abort") 132 | slice1 = pmake([]int, sizeToCheck) 133 | sizeToAbort := transaction.NumEntries*2 + 1 134 | redoTx = transaction.NewRedoTx() 135 | redoTx.Begin() 136 | for i := 0; i < sizeToCheck; i++ { 137 | redoTx.Log(&slice1[i], i) 138 | if i == sizeToAbort { 139 | break 140 | } 141 | } 142 | transaction.Release(redoTx) 143 | for i := 0; i < sizeToCheck; i++ { 144 | assertEqual(t, slice1[i], 0) 145 | } 146 | } 147 | 148 | func TestRedoLogBasic(t *testing.T) { 149 | resetData() 150 | var ( 151 | bTmp interface{} 152 | //ok bool 153 | jTmp interface{} 154 | ) 155 | 156 | fmt.Println("Testing basic data type commit.") 157 | redoTx := transaction.NewRedoTx() 158 | redoTx.Begin() 159 | redoTx.Log(b, true) 160 | redoTx.Log(j, 10) 161 | bTmp = redoTx.ReadLog(b) 162 | jTmp = redoTx.ReadLog(j) 163 | assertEqual(t, *b, false) 164 | assertEqual(t, *j, 0) 165 | assertEqual(t, bTmp.(bool), true) 166 | assertEqual(t, jTmp.(int), 10) 167 | redoTx.End() 168 | assertEqual(t, *b, true) 169 | assertEqual(t, *j, 10) 170 | 171 | fmt.Println("Testing multiple writes to same variable of basic data type.") 172 | redoTx.Begin() 173 | redoTx.Log(b, false) 174 | redoTx.Log(j, 20) 175 | bTmp = redoTx.ReadLog(b) 176 | jTmp = redoTx.ReadLog(j) 177 | assertEqual(t, bTmp.(bool), false) 178 | assertEqual(t, jTmp.(int), 20) 179 | redoTx.Log(b, true) 180 | redoTx.Log(j, 30) 181 | assertEqual(t, *b, true) 182 | assertEqual(t, *j, 10) 183 | bTmp = redoTx.ReadLog(b) 184 | jTmp = redoTx.ReadLog(j) 185 | assertEqual(t, bTmp.(bool), true) 186 | assertEqual(t, jTmp.(int), 30) 187 | redoTx.End() 188 | assertEqual(t, *b, true) 189 | assertEqual(t, *j, 30) 190 | 191 | fmt.Println("Testing basic data type abort.") 192 | redoTx.Begin() 193 | redoTx.Log(b, false) 194 | redoTx.Log(j, 40) 195 | bTmp = redoTx.ReadLog(b) 196 | jTmp = redoTx.ReadLog(j) 197 | assertEqual(t, bTmp.(bool), false) 198 | assertEqual(t, jTmp.(int), 40) 199 | transaction.Release(redoTx) // Calls abort internally 200 | assertEqual(t, *b, true) 201 | assertEqual(t, *j, 30) 202 | 203 | type structRedoBasic struct { 204 | B bool 205 | I int 206 | Iptr *int 207 | S string 208 | } 209 | fmt.Println("Testing basic struct commit.") 210 | basicStruct1 := pnew(structRedoBasic) 211 | basicStruct2 := pnew(structRedoBasic) 212 | basicStruct2.I = 10 213 | basicStruct2.Iptr = &basicStruct2.I 214 | basicStruct2.B = true 215 | basicStruct2.S = "Hello1" 216 | redoTx = transaction.NewRedoTx() 217 | redoTx.Begin() 218 | redoTx.Log(basicStruct1, *basicStruct2) 219 | tmpBasicStruct := redoTx.ReadLog(basicStruct1) 220 | assertEqual(t, tmpBasicStruct.(structRedoBasic), *basicStruct2) 221 | assertEqual(t, basicStruct1.I, 0) 222 | assertEqual(t, basicStruct1.B, false) 223 | assertEqual(t, basicStruct1.S, "") 224 | redoTx.Log(&basicStruct1.S, basicStruct2.S) 225 | redoTx.End() 226 | assertEqual(t, basicStruct1.I, 10) 227 | assertEqual(t, *basicStruct1.Iptr, 10) 228 | assertEqual(t, basicStruct1.B, true) 229 | assertEqual(t, basicStruct1.S, "Hello1") 230 | basicStruct1 = pnew(structRedoBasic) 231 | redoTx = transaction.NewRedoTx() 232 | redoTx.Begin() 233 | redoTx.Log(basicStruct1, *basicStruct2) 234 | tmpBasicStruct = redoTx.ReadLog(basicStruct1) 235 | assertEqual(t, tmpBasicStruct.(structRedoBasic), *basicStruct2) 236 | redoTx.Log(&basicStruct1.S, "Hello2") 237 | redoTx.Log(&basicStruct1.I, 20) 238 | redoTx.End() 239 | assertEqual(t, basicStruct1.I, 20) 240 | assertEqual(t, *basicStruct1.Iptr, 10) // pointing to basicStruct2.I 241 | assertEqual(t, basicStruct1.B, true) 242 | assertEqual(t, basicStruct1.S, "Hello2") 243 | 244 | fmt.Println("Testing basic struct abort.") 245 | basicStruct2.I = 30 246 | basicStruct2.B = false 247 | basicStruct2.S = "Hello3" 248 | redoTx.Begin() 249 | redoTx.Log(basicStruct1, *basicStruct2) 250 | tmpBasicStruct = redoTx.ReadLog(basicStruct1) 251 | assertEqual(t, tmpBasicStruct.(structRedoBasic).I, 30) 252 | assertEqual(t, *(tmpBasicStruct.(structRedoBasic).Iptr), 30) 253 | assertEqual(t, tmpBasicStruct.(structRedoBasic).B, false) 254 | assertEqual(t, tmpBasicStruct.(structRedoBasic).S, "Hello3") 255 | assertEqual(t, basicStruct1.I, 20) 256 | assertEqual(t, *basicStruct1.Iptr, 30) 257 | assertEqual(t, basicStruct1.B, true) 258 | assertEqual(t, basicStruct1.S, "Hello2") 259 | *basicStruct1.Iptr = 40 // redirect update will not rollback 260 | transaction.Release(redoTx) 261 | assertEqual(t, basicStruct1.I, 20) 262 | assertEqual(t, *basicStruct1.Iptr, 40) // pointing to basicStruct2.I 263 | assertEqual(t, basicStruct2.I, 40) // pointing to basicStruct2.I 264 | assertEqual(t, basicStruct1.B, true) 265 | assertEqual(t, basicStruct1.S, "Hello2") 266 | 267 | type structRedoNested struct { 268 | B bool 269 | I int 270 | Iptr *int 271 | S string 272 | Sbasic structRedoBasic 273 | } 274 | fmt.Println("Testing nested struct commit.") 275 | nestedStruct1 := pnew(structRedoNested) 276 | nestedStruct2 := pnew(structRedoNested) 277 | nestedStruct2.I = 10 278 | nestedStruct2.Iptr = &nestedStruct2.I 279 | nestedStruct2.B = true 280 | nestedStruct2.S = "Hello1" 281 | nestedStruct2.Sbasic.I = 100 282 | nestedStruct2.Sbasic.Iptr = &nestedStruct2.Sbasic.I 283 | nestedStruct2.Sbasic.S = "World1" 284 | nestedStruct2.Sbasic.B = true 285 | redoTx = transaction.NewRedoTx() 286 | redoTx.Begin() 287 | redoTx.Log(nestedStruct1, *nestedStruct2) 288 | tmpNestedStruct := redoTx.ReadLog(nestedStruct1) 289 | assertEqual(t, tmpNestedStruct.(structRedoNested), *nestedStruct2) 290 | assertEqual(t, nestedStruct1.I, 0) 291 | assertEqual(t, nestedStruct1.B, false) 292 | assertEqual(t, nestedStruct1.S, "") 293 | assertEqual(t, nestedStruct1.Sbasic.I, 0) 294 | assertEqual(t, nestedStruct1.Sbasic.B, false) 295 | assertEqual(t, nestedStruct1.Sbasic.S, "") 296 | redoTx.End() 297 | assertEqual(t, nestedStruct1.I, 10) 298 | assertEqual(t, *nestedStruct1.Iptr, 10) 299 | assertEqual(t, nestedStruct1.B, true) 300 | assertEqual(t, nestedStruct1.S, "Hello1") 301 | assertEqual(t, nestedStruct1.Sbasic.I, 100) 302 | assertEqual(t, *nestedStruct1.Sbasic.Iptr, 100) 303 | assertEqual(t, nestedStruct1.Sbasic.B, true) 304 | assertEqual(t, nestedStruct1.Sbasic.S, "World1") 305 | nestedStruct1 = pnew(structRedoNested) 306 | redoTx = transaction.NewRedoTx() 307 | redoTx.Begin() 308 | redoTx.Log(nestedStruct1, *nestedStruct2) 309 | redoTx.Log(&nestedStruct1.Sbasic, *basicStruct1) 310 | redoTx.Log(&nestedStruct1.Iptr, &nestedStruct1.I) 311 | assertEqual(t, nestedStruct1.S, "") 312 | assertEqual(t, nestedStruct1.Sbasic.S, "") 313 | redoTx.Log(&nestedStruct1.S, "Hello3") 314 | redoTx.Log(&nestedStruct1.Sbasic.S, "World3") 315 | redoTx.End() 316 | assertEqual(t, nestedStruct1.I, 10) 317 | assertEqual(t, *nestedStruct1.Iptr, 10) 318 | assertEqual(t, nestedStruct1.B, true) 319 | assertEqual(t, nestedStruct1.S, "Hello3") 320 | assertEqual(t, nestedStruct1.Sbasic.I, 20) 321 | assertEqual(t, *nestedStruct1.Sbasic.Iptr, 40) 322 | assertEqual(t, nestedStruct1.Sbasic.B, true) 323 | assertEqual(t, nestedStruct1.Sbasic.S, "World3") 324 | 325 | fmt.Println("Testing nested struct abort.") 326 | nestedStruct2.I = 50 327 | nestedStruct2.Iptr = &nestedStruct2.I 328 | nestedStruct2.B = false 329 | nestedStruct2.S = "Hello4" 330 | nestedStruct2.Sbasic.I = 200 331 | nestedStruct2.Sbasic.Iptr = &nestedStruct2.Sbasic.I 332 | nestedStruct2.Sbasic.S = "World4" 333 | nestedStruct2.Sbasic.B = false 334 | redoTx.Begin() 335 | redoTx.Log(nestedStruct1, *nestedStruct2) 336 | redoTx.Log(&nestedStruct1.Sbasic, *basicStruct2) 337 | redoTx.Log(&nestedStruct1.S, "Hello5") 338 | redoTx.Log(&nestedStruct1.Sbasic.S, "World5") 339 | *nestedStruct1.Sbasic.Iptr = 300 // redirect update will not rollback 340 | transaction.Release(redoTx) 341 | assertEqual(t, nestedStruct1.I, 10) 342 | assertEqual(t, *nestedStruct1.Iptr, 10) 343 | assertEqual(t, nestedStruct1.B, true) 344 | assertEqual(t, nestedStruct1.S, "Hello3") 345 | assertEqual(t, nestedStruct1.Sbasic.I, 20) 346 | assertEqual(t, *nestedStruct1.Sbasic.Iptr, 300) 347 | assertEqual(t, nestedStruct1.Sbasic.B, true) 348 | assertEqual(t, nestedStruct1.Sbasic.S, "World3") 349 | 350 | fmt.Println("Testing slice element update commit.") 351 | slice2[99] = 99 352 | redoTx = transaction.NewRedoTx() 353 | redoTx.Begin() 354 | redoTx.Log(slice1, slice2) 355 | redoTx.Log(&slice1[98], 98) 356 | redoTx.End() 357 | assertEqual(t, slice1[98], 98) 358 | assertEqual(t, slice1[99], 99) 359 | 360 | fmt.Println("Testing slice update abort.") 361 | slice2[9] = 9 362 | redoTx.Begin() 363 | redoTx.Log(slice1[:10], slice2[:10]) 364 | redoTx.Log(&slice1[8], 8) 365 | redoTx.Log(&slice1[12], 12) 366 | transaction.Release(redoTx) 367 | assertEqual(t, slice1[9], 0) 368 | assertEqual(t, slice1[8], 0) 369 | assertEqual(t, slice1[12], 0) 370 | assertEqual(t, slice1[99], 99) 371 | 372 | fmt.Println("Testing read for data not logged before") 373 | redoTx = transaction.NewRedoTx() 374 | redoTx.Begin() 375 | tmpNestedStruct1 := redoTx.ReadLog(nestedStruct1) 376 | redoTx.End() 377 | nestedStruct3 := tmpNestedStruct1.(structRedoNested) 378 | transaction.Release(redoTx) 379 | assertEqual(t, nestedStruct3, *nestedStruct1) 380 | 381 | type structRedoUnexportedField struct { 382 | i int 383 | b bool 384 | iptr *int 385 | uptr unsafe.Pointer 386 | Intf interface{} // okay to log exported interface variables 387 | } 388 | st1 := pnew(structRedoUnexportedField) 389 | st2 := pnew(structRedoUnexportedField) 390 | st1.Intf = 20 391 | st1.i = 10 392 | st1.b = true 393 | st1.iptr = &st1.i 394 | st1.uptr = unsafe.Pointer(&st1.b) 395 | fmt.Println("Testing logging for unexported fields of struct") 396 | redoTx = transaction.NewRedoTx() 397 | redoTx.Begin() 398 | redoTx.Log(st2, *st1) 399 | tmp1 := redoTx.ReadLog(&st2.iptr) 400 | assertEqual(t, tmp1.(*int), st1.iptr) 401 | tmp2 := redoTx.ReadLog(&st2.uptr) 402 | assertEqual(t, tmp2.(unsafe.Pointer), st1.uptr) 403 | tmp3 := redoTx.ReadLog(&st2.Intf) 404 | assertEqual(t, tmp3.(int), 20) 405 | redoTx.End() 406 | assertEqual(t, *st2, *st1) 407 | 408 | fmt.Println("Testing logging for nil values") 409 | doublePtr := pnew(*int) 410 | doublePtr = &st1.iptr 411 | redoTx.Begin() 412 | redoTx.Log(doublePtr, nil) 413 | redoTx.End() 414 | if *doublePtr != nil { 415 | assertEqual(t, 0, 1) // Assert 416 | } 417 | 418 | fmt.Println("Testing read for nil value") 419 | doublePtr1 := pnew(*int) 420 | redoTx.Begin() 421 | tmp1 = redoTx.ReadLog(doublePtr1).(*int) 422 | if tmp1.(*int) != nil { 423 | assertEqual(t, 0, 1) 424 | } 425 | redoTx.End() 426 | transaction.Release(redoTx) 427 | 428 | fmt.Println("Testing slice append commit.") 429 | struct1.slice = pmake([]int, 100) 430 | struct2.slice = pmake([]int, 101) 431 | struct2.slice[100] = 300 432 | redoTx = transaction.NewRedoTx() 433 | redoTx.Begin() 434 | redoTx.Log(&struct1.slice, append(struct1.slice, 200)) // Logs only slicehdr 435 | tmpSlice := redoTx.ReadLog(&struct1.slice).([]int) // slicehdr from log 436 | assertEqual(t, len(tmpSlice), 101) 437 | redoTx.Log(tmpSlice, struct2.slice) 438 | redoTx.End() 439 | assertEqual(t, struct1.slice[100], 300) 440 | assertEqual(t, len(struct1.slice), 101) 441 | 442 | fmt.Println("Testing slice append abort.") 443 | struct2.slice = pmake([]int, 90) 444 | redoTx.Begin() 445 | redoTx.Log(&struct2.slice, append(struct2.slice, 10)) 446 | tmpSlice = redoTx.ReadLog(&struct2.slice).([]int) 447 | assertEqual(t, len(tmpSlice), 91) 448 | redoTx.Log(&tmpSlice[20], 20) 449 | transaction.Release(redoTx) 450 | assertEqual(t, len(struct2.slice), 90) 451 | assertEqual(t, struct2.slice[20], 0) 452 | redoTx = transaction.NewRedoTx() 453 | struct2.slice = pmake([]int, 100) 454 | struct2.slice = append(struct2.slice, 1) 455 | redoTx.Begin() 456 | redoTx.Log(&struct2.slice, append(struct2.slice, 1)) // slicehdr not updated 457 | redoTx.Log(&struct2.slice[30], 30) 458 | transaction.Release(redoTx) 459 | assertEqual(t, len(struct2.slice), 101) 460 | assertEqual(t, struct2.slice[30], 0) 461 | 462 | fmt.Println("Testing error for logging data in volatile memory") 463 | errVolData := errors.New("[redoTx] Log: Updates to data in volatile" + 464 | " memory can be lost") 465 | x := new(int) 466 | redoTx = transaction.NewRedoTx() 467 | redoTx.Begin() 468 | err := redoTx.Log(x, 1) 469 | assertEqual(t, err.Error(), errVolData.Error()) 470 | assertEqual(t, redoTx.ReadLog(x).(int), 1) // got error, but data logged 471 | redoTx.End() 472 | assertEqual(t, *x, 1) // got error, but update still persisted 473 | transaction.Release(redoTx) 474 | redoTx = transaction.NewRedoTx() 475 | redoTx.Begin() 476 | y := make([]int, 10) 477 | z := make([]int, 5) 478 | z[0] = 10 479 | err = redoTx.Log(y, z) 480 | assertEqual(t, err.Error(), errVolData.Error()) 481 | assertEqual(t, redoTx.ReadLog(&y[0]).(int), 10) 482 | redoTx.End() 483 | assertEqual(t, y[0], 10) 484 | transaction.Release(redoTx) 485 | assertEqual(t, y[0], 10) 486 | 487 | fmt.Println("Testing End() return value for inner, outer transaction") 488 | redoTx = transaction.NewRedoTx() 489 | redoTx.Begin() 490 | redoTx.Begin() 491 | b := redoTx.End() 492 | assertEqual(t, b, false) 493 | b = redoTx.End() 494 | assertEqual(t, b, true) 495 | transaction.Release(redoTx) 496 | } 497 | 498 | func TestRedoLogIsolation(t *testing.T) { 499 | resetData() 500 | var wg sync.WaitGroup 501 | chn0 := make(chan int) 502 | chn1 := make(chan int) 503 | fmt.Println("Testing isolation for redo log") 504 | for i := 0; i < 2; i++ { 505 | wg.Add(1) 506 | go func(i int) { 507 | if i == 1 { 508 | <-chn1 509 | } 510 | defer wg.Done() 511 | redoTx := transaction.NewRedoTx() 512 | redoTx.Begin() 513 | tmp := struct1.i // shared variable read 514 | if i == 1 { // Goroutine 1 515 | chn0 <- 1 516 | <-chn1 517 | redoTx.Log(&struct1.i, tmp+100) 518 | redoTx.End() // Oth goroutine doesn't execute this. So it's 519 | // update is rolled back. 1st goroutine read the same variable 520 | // before, but doesn't see Goroutine 1's update in-memory 521 | transaction.Release(redoTx) 522 | } else { // Goroutine 0 523 | redoTx.Log(&struct1.i, tmp+100) // Rolled back subsequently, 524 | // because of tx abort 525 | chn1 <- 1 526 | <-chn0 527 | transaction.Release(redoTx) 528 | chn1 <- 1 529 | } 530 | }(i) 531 | } 532 | wg.Wait() 533 | assertEqual(t, struct1.i, 101) 534 | } 535 | -------------------------------------------------------------------------------- /txtest/txTestsPmemInit.go: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////// 2 | // Copyright 2018-2019 VMware, Inc. 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | /////////////////////////////////////////////////////////////////////// 5 | 6 | package txtest 7 | 8 | import ( 9 | "os" 10 | 11 | "github.com/vmware/go-pmem-transaction/pmem" 12 | ) 13 | 14 | func init() { 15 | os.Remove("tx_testFile") 16 | pmem.Init("tx_testFile") 17 | } 18 | -------------------------------------------------------------------------------- /txtest/txUndoLog_test.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | /////////////////////////////////////////////////////////////////////// 4 | // Copyright 2018-2019 VMware, Inc. 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | /////////////////////////////////////////////////////////////////////// 7 | 8 | package txtest 9 | 10 | import ( 11 | "errors" 12 | "fmt" 13 | "runtime" 14 | "sync" 15 | "testing" 16 | "unsafe" 17 | 18 | "github.com/vmware/go-pmem-transaction/transaction" 19 | ) 20 | 21 | func BenchmarkUndoLogInt(b *testing.B) { 22 | j = pnew(int) 23 | tx := transaction.NewUndoTx() 24 | b.ResetTimer() 25 | for i := 0; i < b.N; i++ { 26 | tx.Begin() 27 | tx.Log(j) 28 | *j = i 29 | tx.End() 30 | } 31 | transaction.Release(tx) 32 | } 33 | 34 | func BenchmarkUndoLogSlice(b *testing.B) { 35 | struct1 = pnew(structLogTest) 36 | struct1.slice = pmake([]int, 10000) 37 | tx := transaction.NewUndoTx() 38 | b.ResetTimer() 39 | for i := 0; i < b.N; i++ { 40 | tx.Begin() 41 | tx.Log(struct1.slice) 42 | struct1.slice[i%10000] = i 43 | tx.End() 44 | } 45 | transaction.Release(tx) 46 | } 47 | 48 | func BenchmarkUndoLogStruct(b *testing.B) { 49 | struct1 = pnew(structLogTest) 50 | struct1.slice = pmake([]int, 10000) 51 | tx := transaction.NewUndoTx() 52 | b.ResetTimer() 53 | for i := 0; i < b.N; i++ { 54 | tx.Begin() 55 | tx.Log(struct1) 56 | struct1.i = i 57 | tx.Log(struct1.slice) 58 | struct1.slice[i%10000] = i 59 | tx.End() 60 | } 61 | transaction.Release(tx) 62 | } 63 | 64 | func BenchmarkUndoLog100Ints(b *testing.B) { 65 | slice1 = pmake([]int, 100) 66 | tx := transaction.NewUndoTx() 67 | b.ResetTimer() 68 | for i := 0; i < b.N; i++ { 69 | tx.Begin() 70 | for c := 0; c < 100; c++ { 71 | tx.Log(&slice1[c]) 72 | slice1[c] = i 73 | } 74 | tx.End() 75 | } 76 | transaction.Release(tx) 77 | } 78 | 79 | func BenchmarkRawInt(b *testing.B) { 80 | j = pnew(int) 81 | b.ResetTimer() 82 | for i := 0; i < b.N; i++ { 83 | *j = i 84 | runtime.PersistRange(unsafe.Pointer(j), unsafe.Sizeof(*j)) 85 | } 86 | } 87 | 88 | func BenchmarkRawSlice(b *testing.B) { 89 | struct1 = pnew(structLogTest) 90 | struct1.slice = pmake([]int, 10000) 91 | b.ResetTimer() 92 | for i := 0; i < b.N; i++ { 93 | struct1.slice[i%10000] = i 94 | runtime.PersistRange(unsafe.Pointer(&struct1.slice[0]), 95 | (uintptr)(len(struct1.slice)* 96 | (int)(unsafe.Sizeof(struct1.slice[0])))) 97 | } 98 | } 99 | 100 | func BenchmarkRawStruct(b *testing.B) { 101 | struct1 = pnew(structLogTest) 102 | struct1.slice = pmake([]int, 10000) 103 | b.ResetTimer() 104 | for i := 0; i < b.N; i++ { 105 | struct1.i = i 106 | struct1.slice[i%100] = i 107 | runtime.PersistRange(unsafe.Pointer(struct1), unsafe.Sizeof(*struct1)) 108 | runtime.PersistRange(unsafe.Pointer(&struct1.slice[0]), 109 | (uintptr)(len(struct1.slice))) 110 | } 111 | } 112 | 113 | func BenchmarkRawReadInt(b *testing.B) { 114 | j = pnew(int) 115 | *j = b.N 116 | b.ResetTimer() 117 | for i := 0; i < b.N; i++ { 118 | a := i 119 | _ = a 120 | } 121 | } 122 | 123 | func TestUndoLogBasic(t *testing.T) { 124 | resetData() 125 | undoTx := transaction.NewUndoTx() 126 | fmt.Println("Testing basic data type commit.") 127 | undoTx.Begin() 128 | undoTx.Log(b) 129 | undoTx.Log(j) 130 | undoTx.Log(&struct1.i) 131 | *b = true 132 | *j = 10 133 | struct1.i = 100 134 | undoTx.End() 135 | assertEqual(t, *b, true) 136 | assertEqual(t, *j, 10) 137 | assertEqual(t, struct1.i, 100) 138 | 139 | fmt.Println("Testing basic data type abort.") 140 | undoTx.Begin() 141 | undoTx.Log(b) 142 | undoTx.Log(j) 143 | *b = false 144 | *j = 0 145 | transaction.Release(undoTx) // Calls abort internally 146 | assertEqual(t, *b, true) 147 | assertEqual(t, *j, 10) 148 | 149 | fmt.Println("Testing basic data type abort after multiple updates.") 150 | undoTx = transaction.NewUndoTx() 151 | undoTx.Begin() 152 | undoTx.Log(j) 153 | *j = 100 154 | *j = 1000 155 | *j = 10000 156 | transaction.Release(undoTx) // Calls abort internally 157 | assertEqual(t, *j, 10) 158 | 159 | fmt.Println("Testing data structure commit.") 160 | undoTx = transaction.NewUndoTx() 161 | undoTx.Begin() 162 | undoTx.Log(struct1) 163 | struct1.i = 10 164 | struct1.iptr = &struct1.i 165 | struct1.slice = slice1 166 | undoTx.Log(struct1.slice) 167 | slice1[0] = 11 168 | undoTx.End() 169 | assertEqual(t, struct1.i, 10) 170 | assertEqual(t, *struct1.iptr, 10) 171 | assertEqual(t, struct1.slice[0], slice1[0]) 172 | undoTx.Begin() 173 | undoTx.Log(struct2) 174 | struct2.slice = slice2 175 | undoTx.Log(struct1) 176 | *struct1 = *struct2 177 | struct1.iptr = &struct1.i 178 | undoTx.Log(struct1.slice) 179 | slice2[0] = 22 180 | undoTx.End() 181 | assertEqual(t, struct1.i, struct2.i) 182 | assertEqual(t, *struct1.iptr, struct2.i) 183 | assertEqual(t, struct1.slice[0], slice2[0]) 184 | 185 | fmt.Println("Testing data structure abort.") 186 | undoTx.Begin() 187 | undoTx.Log(struct1) 188 | struct1.i = 10 189 | struct1.iptr = nil 190 | undoTx.Log(struct1.slice) 191 | struct1.slice = slice1 192 | transaction.Release(undoTx) 193 | assertEqual(t, struct1.i, 2) 194 | assertEqual(t, struct1.iptr, &struct1.i) 195 | assertEqual(t, struct1.slice[0], slice2[0]) 196 | undoTx = transaction.NewUndoTx() 197 | undoTx.Begin() 198 | undoTx.Log(struct1.iptr) 199 | struct1.i = 100 200 | transaction.Release(undoTx) 201 | assertEqual(t, struct1.i, 2) 202 | undoTx = transaction.NewUndoTx() 203 | undoTx.Begin() 204 | struct1.iptr = j 205 | undoTx.End() 206 | undoTx.Begin() 207 | undoTx.Log(struct1) 208 | *(struct1.iptr) = 1000 // redirect update will not rollback 209 | undoTx.Log(struct2) 210 | struct2.i = 3 211 | struct2.iptr = &struct2.i 212 | struct2.slice = slice1 213 | *struct1 = *struct2 214 | transaction.Release(undoTx) 215 | assertEqual(t, struct1.i, 2) 216 | assertEqual(t, *struct1.iptr, 1000) 217 | assertEqual(t, struct1.slice[0], slice2[0]) 218 | 219 | fmt.Println("Testing slice element update commit.") 220 | undoTx = transaction.NewUndoTx() 221 | undoTx.Begin() 222 | undoTx.Log(slice1) 223 | slice1[99] = 99 224 | undoTx.End() 225 | assertEqual(t, slice1[99], 99) 226 | 227 | fmt.Println("Testing slice element update abort.") 228 | undoTx.Begin() 229 | undoTx.Log(slice1[:10]) 230 | slice2[9] = 9 231 | slice2[10] = 10 // out of range update will not rollback 232 | copy(slice1, slice2) 233 | transaction.Release(undoTx) 234 | assertEqual(t, slice1[9], 0) 235 | assertEqual(t, slice1[10], 10) 236 | assertEqual(t, slice1[99], 0) 237 | 238 | fmt.Println("Testing slice append commit.") 239 | struct1.slice = pmake([]int, 100) 240 | undoTx = transaction.NewUndoTx() 241 | undoTx.Begin() 242 | undoTx.Log(&struct1.slice) // This would log slice header & slice elements 243 | struct1.slice[10] = 11 244 | struct1.slice = append(struct1.slice, 101) 245 | struct1.slice[100] = 101 246 | undoTx.End() 247 | assertEqual(t, struct1.slice[10], 11) 248 | assertEqual(t, struct1.slice[100], 101) 249 | assertEqual(t, len(struct1.slice), 101) 250 | 251 | fmt.Println("Testing slice append abort.") 252 | struct2.slice = pmake([]int, 90) 253 | undoTx.Begin() 254 | undoTx.Log(&struct2.slice) 255 | struct2.slice[10] = 10 256 | struct2.slice = append(struct2.slice, 1) // causes slice header update 257 | transaction.Release(undoTx) 258 | assertEqual(t, len(struct2.slice), 90) 259 | assertEqual(t, struct2.slice[10], 0) 260 | undoTx = transaction.NewUndoTx() 261 | struct2.slice = pmake([]int, 100) 262 | struct2.slice = append(struct2.slice, 1) 263 | undoTx.Begin() 264 | undoTx.Log(&struct2.slice) 265 | struct2.slice = append(struct2.slice, 1) // no slice header update 266 | struct2.slice[20] = 20 267 | transaction.Release(undoTx) 268 | assertEqual(t, len(struct2.slice), 101) 269 | assertEqual(t, struct2.slice[20], 0) 270 | 271 | fmt.Println("Testing error for logging data in volatile memory") 272 | errVolData := errors.New("[undoTx] Log: Can't log data in volatile memory") 273 | x := new(int) 274 | undoTx = transaction.NewUndoTx() 275 | undoTx.Begin() 276 | err := undoTx.Log(x) 277 | assertEqual(t, err.Error(), errVolData.Error()) 278 | *x = 1 279 | transaction.Release(undoTx) 280 | assertEqual(t, *x, 1) // x was not logged, so update not rolled back 281 | undoTx = transaction.NewUndoTx() 282 | undoTx.Begin() 283 | y := make([]int, 10) 284 | err = undoTx.Log(y) 285 | assertEqual(t, err.Error(), errVolData.Error()) 286 | y[0] = 10 287 | transaction.Release(undoTx) 288 | assertEqual(t, y[0], 10) 289 | 290 | fmt.Println("Testing data structure commit when undoTx.Log() does update") 291 | undoTx = transaction.NewUndoTx() 292 | undoTx.Begin() 293 | undoTx.Log(struct1, structLogTest{10, &struct1.i, slice1}) 294 | slice1[0] = 11 295 | undoTx.End() 296 | assertEqual(t, struct1.i, 10) 297 | assertEqual(t, *struct1.iptr, 10) 298 | assertEqual(t, struct1.slice[0], slice1[0]) 299 | 300 | fmt.Println("Testing data structure abort when undoTx.Log() does update") 301 | undoTx.Begin() 302 | undoTx.Log(struct1, structLogTest{20, &struct1.i, slice1}) 303 | slice1[0] = 22 304 | transaction.Release(undoTx) 305 | assertEqual(t, struct1.i, 10) 306 | assertEqual(t, *struct1.iptr, 10) 307 | assertEqual(t, struct1.slice[0], slice1[0]) 308 | 309 | fmt.Println("Testing variable update when the new value is nil") 310 | undoTx = transaction.NewUndoTx() 311 | undoTx.Begin() 312 | undoTx.Log(&struct1, nil) 313 | undoTx.End() 314 | if struct1 != nil { 315 | assertEqual(t, 0, 1) // Assert 316 | } 317 | 318 | fmt.Println("Testing logging when old slice value is empty") 319 | struct2.slice = pmake([]int, 0, 0) 320 | assertEqual(t, len(struct2.slice), 0) 321 | undoTx.Begin() 322 | undoTx.Log(&slice2[2], 10) 323 | undoTx.Log(&struct2.slice, slice2) 324 | undoTx.End() 325 | transaction.Release(undoTx) 326 | assertEqual(t, struct2.slice[2], slice2[2]) 327 | assertEqual(t, len(struct2.slice), len(slice2)) 328 | 329 | fmt.Println("Testing End() return value for inner, outer transaction") 330 | undoTx = transaction.NewUndoTx() 331 | undoTx.Begin() 332 | undoTx.Begin() 333 | b := undoTx.End() 334 | assertEqual(t, b, false) 335 | b = undoTx.End() 336 | assertEqual(t, b, true) 337 | transaction.Release(undoTx) 338 | 339 | fmt.Println("Testing abort when nil slice is logged") 340 | struct1 = pnew(structLogTest) 341 | undoTx = transaction.NewUndoTx() 342 | undoTx.Begin() 343 | undoTx.Log(&struct1.slice) 344 | struct1.slice = pmake([]int, 2) 345 | transaction.Release(undoTx) // <-- abort 346 | if struct1.slice != nil { 347 | assertEqual(t, 0, 1) // Assert 348 | } 349 | } 350 | 351 | func TestUndoLogRead(t *testing.T) { 352 | resetData() 353 | tx := transaction.NewUndoTx() 354 | 355 | struct1.slice = pmake([]int, 10) 356 | struct2.slice = pmake([]int, 100) 357 | struct1.slice[0] = 9 358 | struct2.slice[0] = 2 359 | struct2.slice[20] = 1 360 | 361 | fmt.Println("Testing TX.Readlog with 1 arg (ptr)") 362 | tx.Begin() 363 | // struct1.i = struct2.slice[0]+3 364 | tx.Log(&struct1.i, tx.ReadLog(&struct2.slice[0]).(int)+3) 365 | tx.End() 366 | assertEqual(t, struct1.i, 5) 367 | 368 | fmt.Println("Testing TX.Readlog with 2 args (read slice element)") 369 | tx.Begin() 370 | // struct1.slice = struct2.slice 371 | tx.Log(&struct1.slice, struct2.slice) 372 | // *j = struct1.slice[20] + 2 373 | tx.Log(j, tx.ReadLog(&struct1.slice, 20).(int)+2) 374 | tx.End() 375 | assertEqual(t, *j, 3) 376 | assertEqual(t, len(struct1.slice), 100) 377 | assertEqual(t, struct1.slice[0], 2) 378 | 379 | fmt.Println("Testing TX.Readlog with 3 args (read slice)") 380 | struct2.slice[90] = 90 381 | tx.Begin() 382 | // struct1.slice = struct2.slice[88:92] 383 | tx.Log(&struct1.slice, tx.ReadLog(&struct2.slice, 88, 92).([]int)) 384 | // *j = struct1.slice[2] 385 | tx.Log(j, tx.ReadLog(&struct1.slice, 2).(int)+1) 386 | tx.End() 387 | transaction.Release(tx) 388 | assertEqual(t, len(struct1.slice), 4) 389 | assertEqual(t, struct1.slice[2], 90) 390 | assertEqual(t, *j, 91) 391 | } 392 | 393 | func TestUndoLogExpand(t *testing.T) { 394 | fmt.Println("Testing undo log expansion commit by logging more entries") 395 | undoTx := transaction.NewUndoTx() 396 | sizeToCheck := transaction.NumEntries*4 + 1 397 | slice1 = pmake([]int, sizeToCheck) 398 | undoTx.Begin() 399 | for i := 0; i < sizeToCheck; i++ { 400 | undoTx.Log(&slice1[i]) 401 | slice1[i] = i 402 | } 403 | undoTx.End() 404 | transaction.Release(undoTx) 405 | for i := 0; i < sizeToCheck; i++ { 406 | assertEqual(t, slice1[i], i) 407 | } 408 | 409 | fmt.Println("Testing undo log expansion abort") 410 | slice1 = pmake([]int, sizeToCheck) 411 | sizeToAbort := transaction.NumEntries*2 + 1 412 | undoTx = transaction.NewUndoTx() 413 | undoTx.Begin() 414 | for i := 0; i < sizeToCheck; i++ { 415 | undoTx.Log(&slice1[i]) 416 | slice1[i] = i 417 | if i == sizeToAbort { 418 | break 419 | } 420 | } 421 | transaction.Release(undoTx) 422 | for i := 0; i < sizeToCheck; i++ { 423 | assertEqual(t, slice1[i], 0) 424 | } 425 | } 426 | 427 | func TestUndoLogConcurrent(t *testing.T) { 428 | resetData() 429 | fmt.Println("Testing concurrent logging") 430 | m1 := new(sync.RWMutex) 431 | m2 := new(sync.RWMutex) 432 | var wg sync.WaitGroup 433 | fmt.Println("Before:", struct1.i, struct2.i) 434 | for i := 0; i < 1000; i++ { 435 | wg.Add(1) 436 | go func(i int) { 437 | defer wg.Done() 438 | undoTx := transaction.NewUndoTx() 439 | undoTx.Begin() 440 | 441 | // This is a bad way of taking locks inside transaction. 442 | // Used this way here for testing 443 | m1.Lock() 444 | undoTx.Log(struct1) 445 | struct1.i = i 446 | m1.Unlock() 447 | m2.Lock() 448 | undoTx.Log(struct2) 449 | struct2.i = i 450 | m2.Unlock() 451 | undoTx.End() 452 | 453 | transaction.Release(undoTx) 454 | }(i) 455 | } 456 | wg.Wait() 457 | assertEqual(t, struct1.i, struct2.i) 458 | fmt.Println("After:", struct1.i, struct2.i) 459 | } 460 | 461 | func TestUndoLogNoIsolation(t *testing.T) { 462 | resetData() 463 | var wg sync.WaitGroup 464 | chn0 := make(chan int) 465 | chn1 := make(chan int) 466 | fmt.Println("Testing no isolation for undo log") 467 | for i := 0; i < 2; i++ { 468 | wg.Add(1) 469 | go func(i int) { 470 | if i == 1 { 471 | <-chn1 472 | } 473 | defer wg.Done() 474 | undoTx := transaction.NewUndoTx() 475 | undoTx.Begin() 476 | undoTx.Log(struct1) 477 | tmp := struct1.i // shared variable read 478 | if i == 1 { // Goroutine 1 479 | chn0 <- 1 480 | <-chn1 481 | struct1.i = tmp + 100 482 | undoTx.End() // Oth goroutine doesn't execute this. So it's 483 | // update is rolled back. But 1st goroutine already 484 | // saw the update through tmp variable 485 | transaction.Release(undoTx) 486 | } else { // Goroutine 0 487 | struct1.i = tmp + 100 // Rolled back subsequently, but update 488 | // is visible immediately, before tx commit 489 | chn1 <- 1 490 | <-chn0 491 | transaction.Release(undoTx) 492 | chn1 <- 1 493 | } 494 | }(i) 495 | } 496 | wg.Wait() 497 | assertEqual(t, struct1.i, 201) 498 | } 499 | --------------------------------------------------------------------------------