Repository: juicedata/juicefs
Pull request #2016 (2022-05-13 08:11:07)

proofread: s3_gateway.md
proofread: s3_gateway.md

Fix: https://github.com/juicedata/docs/issues/40

Hi @AIXjing , would you please review this PR when you get time? Thank you~


Patch:
diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml
new file mode 100644
index 00000000..a2ca50b5
--- /dev/null
+++ b/.github/workflows/config.yml
@@ -0,0 +1,93 @@
+name: "config-test"
+
+on:
+  push:
+    branches: 
+      - main
+      - release-**
+    paths-ignore:
+      - 'docs/**'
+      - '**.md'
+  pull_request:
+    #The branches below must be a subset of the branches above
+    branches: 
+      - main
+      - release-**
+    paths-ignore:
+      - 'docs/**'
+      - '**.md'
+  workflow_dispatch:
+  
+jobs:
+  config-test:
+    runs-on: ubuntu-latest
+    services:
+      redis:
+        # Docker Hub image
+        image: redis
+        # Set health checks to wait until redis has started
+        options: >-
+          --health-cmd "redis-cli ping"
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+        ports:
+          # Maps port 6379 on service container to the host
+          - 6379:6379
+    steps:
+      - name: Set up Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: '1.17.x'
+
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+
+      - name: Build linux target
+        run: make juicefs
+
+      - name: Setup minio
+        run: |
+            docker run -d -p 9000:9000 --name minio \
+                     -e "MINIO_ACCESS_KEY=minioadmin" \
+                     -e "MINIO_SECRET_KEY=minioadmin" \
+                     -v /tmp/data:/data \
+                     -v /tmp/config:/root/.minio \
+                     minio/minio server /data     
+                     
+      - name: Download tool
+        run: | 
+          wget -q https://dl.minio.io/client/mc/release/linux-amd64/mc
+          chmod +x mc 
+          
+      - name: Test
+        run: |
+          meta_url=redis://127.0.0.1/1
+          mount_point=/tmp/sync-test/
+          ./mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin
+          ./mc admin user add minio juicedata juicedata
+          ./mc admin policy set minio consoleAdmin user=juicedata
+          ./juicefs format --storage minio --bucket http://localhost:9000/jfs-test --access-key juicedata --secret-key juicedata $meta_url myjfs
+          ./juicefs mount $meta_url $mount_point -d --io-retries 1
+
+          ./mc admin user remove minio juicedata
+          ./mc admin user add minio juicedata1 juicedata1
+          ./mc admin policy set minio consoleAdmin user=juicedata1
+          ./juicefs config $meta_url --access-key juicedata1 --secret-key juicedata1
+          sleep 60
+          echo abc > abc.txt
+          cp abc.txt $mount_point/abc.txt && echo "write success"
+          cat $mount_point/abc.txt | grep abc && echo "read success"
+
+      - name: Send Slack Notification
+        if: ${{ failure() }}
+        uses: juicedata/slack-notify-action@main
+        with:
+          channel-id: "${{ secrets.SLACK_CHANNEL_ID_FOR_PR_CHECK_NOTIFY }}"
+          slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"  
+
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
\ No newline at end of file
diff --git a/.github/workflows/fio_benchmark.yml b/.github/workflows/fio_benchmark.yml
index 1287c76b..36c1c719 100644
--- a/.github/workflows/fio_benchmark.yml
+++ b/.github/workflows/fio_benchmark.yml
@@ -52,7 +52,10 @@ jobs:
         run: |
           export PYROSCOPE_AUTH_TOKEN="${{ secrets.PYROSCOPE_AUTH_TOKEN }}"  
           size=${{ github.event.inputs.size }}
+          test -z "$size" && size='1G' 
           nrfiles=${{ github.event.inputs.nrfiles }}
+          test -z "$nrfiles" && nrfiles=10000
+
           disk_warning_value=90
           decrease_ratio=0.8
           fio_jobs=(
diff --git a/.github/workflows/gateway.yml b/.github/workflows/gateway.yml
new file mode 100644
index 00000000..034cd94e
--- /dev/null
+++ b/.github/workflows/gateway.yml
@@ -0,0 +1,155 @@
+name: "gateway-test"
+
+on:
+  push:
+    branches: 
+      - main
+      - release-**
+    paths-ignore:
+      - 'docs/**'
+      - '**.md'
+  pull_request:
+    #The branches below must be a subset of the branches above
+    branches: 
+      - main
+      - release-**
+    paths-ignore:
+      - 'docs/**'
+      - '**.md'
+  workflow_dispatch:
+
+jobs:
+  upload-small-files:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+
+      - name: Build linux target
+        run: |
+          make juicefs
+
+      - name: Test copy small files with gateway
+        run: | 
+          wget -q https://dl.minio.io/client/mc/release/linux-amd64/mc
+          chmod +x mc 
+          sudo apt install md5deep
+          sudo /etc/init.d/mysql start
+
+          meta_url="mysql://root:root@(127.0.0.1)/test_gateway"
+          db_name=$(basename $meta_url)
+          volume=myjfs
+          mp=/tmp/myjfs
+          mysql -uroot -proot -e "drop database if exists $db_name; create database $db_name;" 
+          export MINIO_ROOT_USER=minioadmin
+          export MINIO_ROOT_PASSWORD=minioadmin
+          ./juicefs format $meta_url $volume
+          ./juicefs gateway $meta_url localhost:9000 &
+
+          dd if=/dev/urandom of=file iflag=fullblock,count_bytes bs=4k count='400K' > /dev/null
+          mkdir data
+          for i in {1..1000}; do
+            cp file data/file$i
+          done
+          md5deep -lr data/ > sum.md5
+
+          ./juicefs sync --dirs data/  s3://minioadmin:minioadmin@localhost:9000/$volume/data/ --no-https -p 50
+          #./mc alias set minio http://localhost:9000 minioadmin minioadmin --api S3v4
+          #./mc mb minio/$volume
+          #./mc cp --recursive data/  minio/$volume/data
+
+          ./juicefs mount -d $meta_url $mp
+          count=$(md5deep -lrX sum.md5 || wc -l)
+          [ $count = 0 ] && echo "check md5 succeed" || echo "check md5 failed"
+
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
+
+  upload-big-files:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+
+      - name: Build linux target
+        run: |
+          make juicefs
+
+      - name: Test copy big files with gateway
+        run: | 
+          wget -q https://dl.minio.io/client/mc/release/linux-amd64/mc
+          chmod +x mc 
+          sudo apt install md5deep
+          sudo /etc/init.d/mysql start
+
+          meta_url="mysql://root:root@(127.0.0.1)/test_gateway"
+          db_name=$(basename $meta_url)
+          volume=myjfs
+          mp=/tmp/myjfs
+          mysql -uroot -proot -e "drop database if exists $db_name; create database $db_name;" 
+          export MINIO_ROOT_USER=minioadmin
+          export MINIO_ROOT_PASSWORD=minioadmin
+          ./juicefs format $meta_url $volume
+          ./juicefs gateway $meta_url localhost:9000 &
+
+          dd if=/dev/urandom of=file iflag=fullblock,count_bytes bs=4k count='1G' > /dev/null
+          mkdir data
+          for i in {1..5}; do
+            cp file data/file_$i
+          done          
+          md5deep -lr data/ > sum.md5
+
+          ./juicefs sync --dirs data/  s3://minioadmin:minioadmin@localhost:9000/$volume/data/ --no-https -p 50
+          #./mc alias set minio http://localhost:9000 minioadmin minioadmin --api S3v4
+          #./mc mb minio/$volume
+          #./mc cp --recursive data/  minio/$volume/data
+          
+          ./juicefs mount -d $meta_url $mp
+          count=$(md5deep -lrX sum.md5 || wc -l)
+          [ $count = 0 ] && echo "check md5 succeed" || echo "check md5 failed"
+
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
+
+  sync-with-multithread:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+
+      - name: Build linux target
+        run: |
+          make juicefs
+
+      - name: Test sync to gateway with multithread
+        run: | 
+          #test for PR: https://github.com/juicedata/juicefs/pull/2024
+          wget -q https://dl.minio.io/client/mc/release/linux-amd64/mc
+          chmod +x mc 
+          sudo apt install md5deep
+          sudo /etc/init.d/mysql start
+
+          meta_url="mysql://root:root@(127.0.0.1)/test_gateway"
+          volume=myjfs
+          mp=/tmp/myjfs
+          db_name=$(basename $meta_url)
+          mysql -uroot -proot -e "drop database if exists $db_name; create database $db_name;" 
+          export MINIO_ROOT_USER=minioadmin
+          export MINIO_ROOT_PASSWORD=minioadmin
+          ./juicefs format $meta_url $volume
+          ./juicefs gateway $meta_url localhost:9000 &
+          ./juicefs sync --dirs ./  s3://minioadmin:minioadmin@localhost:9000/$volume/source_dir/ --no-https -p 20
+          ./juicefs mount -d $meta_url $mp
+          rm -rf $mp/source_dir
+
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
\ No newline at end of file
diff --git a/.github/workflows/list.yml b/.github/workflows/list.yml
new file mode 100644
index 00000000..b4f3d1ac
--- /dev/null
+++ b/.github/workflows/list.yml
@@ -0,0 +1,206 @@
+name: "list-test"
+
+on:
+  push:
+    branches:
+      - 'release-**'
+    paths-ignore:
+      - 'docs/**'
+  pull_request:
+    #The branches below must be a subset of the branches above
+    branches:
+      - 'release-**'
+    paths-ignore:
+      - 'docs/**'
+  schedule:
+    - cron:  '0 3 * * *'
+  workflow_dispatch:
+
+jobs:
+  pg-list:
+    runs-on: ubuntu-latest
+    services:
+      postgres:
+        image: postgres
+        env:
+          POSTGRES_PASSWORD: postgres
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+        ports:
+          - 5432:5432
+    steps:
+      - uses: actions/checkout@v3
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+      - name: Init
+        run: |
+          sudo chmod 777 /var
+          git clone https://github.com/sanwan/actionfile.git
+          cd actionfile
+          cat META_7M_EMPTY_FILE.json.gz* >META_7M_EMPTY_FILE.json.gz
+          gzip -dk  META_7M_EMPTY_FILE.json.gz
+          ls -lh
+          mv META_7M_EMPTY_FILE.json ../
+          cd ../
+          ls -lh
+      - name: Load
+        uses: ./.github/actions/load
+        with:
+          meta_url: "postgres://postgres:postgres@127.0.0.1:5432/sync_test?sslmode=disable" 
+          load_file: "META_7M_EMPTY_FILE.json"
+      - name: List
+        run: |
+          cd /tmp/juicefs-sync-test/createfile/
+          ls -lh |wc -l >/tmp/count.log
+          cat /tmp/count.log | grep 7000001
+          if [ $? != 0 ];then echo "file num is error";exit 1; fi
+      - name: Send Slack Notification
+        if: ${{ failure() }}
+        uses: juicedata/slack-notify-action@main
+        with:
+          channel-id: "${{ secrets.SLACK_CHANNEL_ID_FOR_PR_CHECK_NOTIFY }}"
+          slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
+
+
+  mysql-list:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+      - name: Init
+        run: |
+          sudo chmod 777 /var
+          sudo /etc/init.d/mysql start
+          git clone https://github.com/sanwan/actionfile.git
+          cd actionfile
+          cat META_7M_EMPTY_FILE.json.gz* >META_7M_EMPTY_FILE.json.gz
+          gzip -dk  META_7M_EMPTY_FILE.json.gz
+          ls -lh
+          mv META_7M_EMPTY_FILE.json ../
+          cd ../
+          ls -lh
+      - name: Load
+        uses: ./.github/actions/load
+        with:
+          meta_url: "mysql://root:root@\\(127.0.0.1\\)/sync_test" 
+          load_file: "META_7M_EMPTY_FILE.json"
+      - name: List
+        run: |
+          cd /tmp/juicefs-sync-test/createfile/
+          ls -lh |wc -l >/tmp/count.log
+          cat /tmp/count.log | grep 7000001
+          if [ $? != 0 ];then echo "file num is error";exit 1; fi
+      - name: Send Slack Notification
+        if: ${{ failure() }}
+        uses: juicedata/slack-notify-action@main
+        with:
+          channel-id: "${{ secrets.SLACK_CHANNEL_ID_FOR_PR_CHECK_NOTIFY }}"
+          slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
+
+  redis-list:
+    runs-on: ubuntu-latest
+    services:
+      redis:
+        # Docker Hub image
+        image: redis
+        # Set health checks to wait until redis has started
+        options: >-
+          --health-cmd "redis-cli ping"
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+        ports:
+          # Maps port 6379 on service container to the host
+          - 6379:6379
+    steps:
+      - uses: actions/checkout@v3
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+      - name: Init
+        run: |
+          sudo chmod 777 /var
+          git clone https://github.com/sanwan/actionfile.git
+          cd actionfile
+          cat META_7M_EMPTY_FILE.json.gz* >META_7M_EMPTY_FILE.json.gz
+          gzip -dk  META_7M_EMPTY_FILE.json.gz
+          ls -lh
+          mv META_7M_EMPTY_FILE.json ../
+          cd ../
+          ls -lh
+      - name: Load
+        uses: ./.github/actions/load
+        with:
+          meta_url: redis://127.0.0.1:6379/1 
+          load_file: "META_7M_EMPTY_FILE.json"
+      - name: List
+        run: |
+          cd /tmp/juicefs-sync-test/createfile/
+          ls -lh |wc -l >/tmp/count.log
+          cat /tmp/count.log | grep 7000001
+          if [ $? != 0 ];then echo "file num is error";exit 1; fi
+      - name: Send Slack Notification
+        if: ${{ failure() }}
+        uses: juicedata/slack-notify-action@main
+        with:
+          channel-id: "${{ secrets.SLACK_CHANNEL_ID_FOR_PR_CHECK_NOTIFY }}"
+          slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
+
+
+  sqlite3-list:
+    runs-on: ubuntu-latest   
+    steps:
+      - uses: actions/checkout@v3
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+      - name: Init
+        run: |
+          sudo chmod 777 /var
+          git clone https://github.com/sanwan/actionfile.git
+          cd actionfile
+          cat META_7M_EMPTY_FILE.json.gz* >META_7M_EMPTY_FILE.json.gz
+          gzip -dk  META_7M_EMPTY_FILE.json.gz
+          ls -lh
+          mv META_7M_EMPTY_FILE.json ../
+          cd ../
+          ls -lh
+      - name: Load
+        uses: ./.github/actions/load
+        with:
+          meta_url: sqlite3://sync-test.db
+          load_file: "META_7M_EMPTY_FILE.json"
+      - name: List
+        run: |
+          cd /tmp/juicefs-sync-test/createfile/
+          ls -lh |wc -l >/tmp/count.log
+          cat /tmp/count.log | grep 7000001
+          if [ $? != 0 ];then echo "file num is error";exit 1; fi
+      - name: Send Slack Notification
+        if: ${{ failure() }}
+        uses: juicedata/slack-notify-action@main
+        with:
+          channel-id: "${{ secrets.SLACK_CHANNEL_ID_FOR_PR_CHECK_NOTIFY }}"
+          slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
\ No newline at end of file
diff --git a/.github/workflows/version_compatible.yml b/.github/workflows/version_compatible.yml
index 715489ee..7359a5f3 100644
--- a/.github/workflows/version_compatible.yml
+++ b/.github/workflows/version_compatible.yml
@@ -50,7 +50,6 @@ jobs:
                      minio/minio server /data
           wget -q https://dl.minio.io/client/mc/release/linux-amd64/mc
           chmod +x mc
-          ./mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin
 
       - name: Set Variable
         id: vars
@@ -68,6 +67,7 @@ jobs:
 
       - name: Version Compatible Test
         run: |
+          ./mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin
           tests=(
               "format:jfs_new, format:jfs_old, destroy:jfs_new"
               "format:jfs_new, format:jfs_old, destroy:jfs_old"
diff --git a/cmd/main.go b/cmd/main.go
index 2802dd33..6c7c5307 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -77,7 +77,6 @@ func Main(args []string) error {
 	}
 
 	return app.Run(reorderOptions(app, args))
-
 }
 
 func handleSysMountArgs(args []string) []string {
@@ -219,7 +218,7 @@ func reorderOptions(app *cli.App, args []string) []string {
 // Check number of positional arguments, set logger level and setup agent if needed
 func setup(c *cli.Context, n int) {
 	if c.NArg() < n {
-		logger.Errorf("This command requires at least %d arguments", n)
+		fmt.Printf("ERROR: This command requires at least %d arguments\n", n)
 		fmt.Printf("USAGE:\n   juicefs %s [command options] %s\n", c.Command.Name, c.Command.ArgsUsage)
 		os.Exit(1)
 	}
diff --git a/cmd/mount.go b/cmd/mount.go
index c954d97c..3c506ce4 100644
--- a/cmd/mount.go
+++ b/cmd/mount.go
@@ -91,10 +91,12 @@ func installHandler(mp string) {
 	signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
 	go func() {
 		for {
-			<-signalChan
+			sig := <-signalChan
+			logger.Infof("Received signal %s, exiting...", sig.String())
 			go func() { _ = doUmount(mp, true) }()
 			go func() {
 				time.Sleep(time.Second * 3)
+				logger.Warnf("Umount not finished after 3 seconds, force exit")
 				os.Exit(1)
 			}()
 		}
diff --git a/cmd/status.go b/cmd/status.go
index 0baec2b4..e3ff4bc3 100644
--- a/cmd/status.go
+++ b/cmd/status.go
@@ -73,7 +73,7 @@ func status(ctx *cli.Context) error {
 	format.RemoveSecret()
 
 	if sid := ctx.Uint64("session"); sid != 0 {
-		s, err := m.GetSession(sid)
+		s, err := m.GetSession(sid, true)
 		if err != nil {
 			logger.Fatalf("get session: %s", err)
 		}
diff --git a/docs/en/deployment/s3_gateway.md b/docs/en/deployment/s3_gateway.md
index 72433092..da451dde 100644
--- a/docs/en/deployment/s3_gateway.md
+++ b/docs/en/deployment/s3_gateway.md
@@ -5,9 +5,9 @@ slug: /s3_gateway
 ---
 # Deploy JuiceFS S3 Gateway
 
-JuiceFS has introduced S3 gateway since v0.11, a feature implemented through the [MinIO S3 Gateway](https://docs.min.io/docs/minio-gateway-for-s3.html). It provides an S3-compatible RESTful API for files on JuiceFS, enabling the management of files stored on JuiceFS with tools such as s3cmd, AWS CLI, MinIO Client (mc), etc. in cases where mounting is not convenient. In addition, S3 gateway also provides a web-based file manager that allows users to use a browser to manage the files on JuiceFS.
+JuiceFS has introduced S3 gateway since v0.11. The feature is implemented based on the [MinIO S3 Gateway](https://docs.min.io/docs/minio-gateway-for-s3.html). It provides an S3-compatible RESTful API for files on JuiceFS, enabling the management of files stored on JuiceFS with tools such as s3cmd, AWS CLI, and MinIO Client (mc) in cases where mounting is not convenient. In addition, S3 gateway also provides a web-based file manager that allows users to manage files in web browsers.
 
-Because JuiceFS stores files in chunks, the files cannot be accessed directly using the interface of the underlying object storage. The S3 gateway provides similar access to the underlying object storage, with the following architecture diagram.
+Since JuiceFS stores files in chunks, the files cannot be accessed directly through the interfaces of the underlying object storage. The S3 gateway accesses the underlying object storage in a similar way, shown in the following architecture diagram.
 
 ![](../images/juicefs-s3-gateway-arch.png)
 
@@ -15,11 +15,11 @@ Because JuiceFS stores files in chunks, the files cannot be accessed directly us
 
 The S3 gateway is a feature built on top of the JuiceFS file system. If you do not have a JuiceFS file system, please refer to the [quick start guide](../getting-started/for_local.md) to create one first.
 
-JuiceFS S3 gateway is a feature introduced in v0.11, please make sure you have the latest version of JuiceFS.
+JuiceFS S3 gateway is a feature introduced since v0.11. Please make sure you have the latest version of JuiceFS.
 
 ## Quickstart
 
-The S3 gateway can be enabled on the current host using the `gateway` subcommand of JuiceFS. Before enabling the feature, you need to set `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` environment variables, which are the Access Key and Secret Key used to authenticate when accessing the S3 API, and can be simply considered as the username and password of the S3 gateway. For example.
+The S3 gateway can be enabled on the current host using the `gateway` subcommand of JuiceFS. Before enabling the feature, you need to set the environment variables `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD`. These are the Access Key and Secret Key for authenticating when accessing the S3 API, and can be simply considered as the username and password of the S3 gateway. For example.
 
 ```shell
 $ export MINIO_ROOT_USER=admin
@@ -27,19 +27,19 @@ $ export MINIO_ROOT_PASSWORD=12345678
 $ juicefs gateway redis://localhost:6379 localhost:9000
 ```
 
-Among the above three commands, the first two commands are used to set environment variables. Note that the length of `MINIO_ROOT_USER` is at least 3 characters, and the length of `MINIO_ROOT_PASSWORD` is at least 8 characters (Windows users should set the environment variable with the `set` command instead, e.g., `set MINIO_ROOT_USER=admin`).
+The first two commands of the above three are used to set environment variables. Note that the length of `MINIO_ROOT_USER` is at least 3 characters, and the length of `MINIO_ROOT_PASSWORD` is at least 8 characters. If you are a Windows user, replace `export` with `set` in the above commands to set the environment variable. i.e., `set MINIO_ROOT_USER=admin`.
 
-The last command is used to enable the S3 gateway. The `gateway` subcommand requires at least two parameters, the first is the URL of the database where the metadata is stored, and the second is the address and port on which the S3 gateway is listening. You can add [other options](../reference/command_reference.md#juicefs-gateway) to the `gateway` subcommand to optimize the S3 gateway as needed, for example, to set the default local cache to 20 GiB.
+The last command is used to enable the S3 gateway. The `gateway` subcommand requires at least two parameters. The first is the URL of the database where the metadata is stored, and the second is the address and port on which the S3 gateway is listening. You can add [other options](../reference/command_reference.md#juicefs-gateway) to the `gateway` subcommand to optimize the S3 gateway as needed, for example, to set the default local cache to 20 GiB.
 
 ```shell
 $ juicefs gateway --cache-size 20480 redis://localhost:6379 localhost:9000
 ```
 
-In this example, we assume that the JuiceFS file system is using a local Redis database. When the S3 gateway is enabled, the administrative interface of the S3 gateway can be accessed on the **current host** using the address `http://localhost:9000`.
+In this example, we assume that the JuiceFS file system is using a local Redis database. When the S3 gateway is enabled, the administrative interface of the S3 gateway can be accessed from the **current host** using the address `http://localhost:9000`.
 
 ![](../images/s3-gateway-file-manager.jpg)
 
-If you want to access the S3 gateway through other hosts on the LAN or the Internet, you need to adjust the listening address, e.g.
+If you want to access the S3 gateway from other hosts on the LAN or over the Internet, you need to change the listening address, e.g.
 
 ```shell
 $ juicefs gateway redis://localhost:6379 0.0.0.0:9000
@@ -47,11 +47,11 @@ $ juicefs gateway redis://localhost:6379 0.0.0.0:9000
 
 In this way, the S3 gateway will accept all network requests by default. S3 clients in different locations can access the S3 gateway using different addresses, e.g.
 
-- Third-party clients in the host where the S3 gateway is located can use `http://127.0.0.1:9000` or `http://localhost:9000` for access.
+- A third-party client in the host where the S3 gateway is located can use `http://127.0.0.1:9000` or `http://localhost:9000` for access.
 - A third-party client on the same LAN as the host where the S3 gateway is located can access it using `http://192.168.1.8:9000` (assuming the intranet IP address of the S3 gateway-enabled host is 192.168.1.8).
-- The S3 gateway can be accessed via the Internet using `http://110.220.110.220:9000` (assuming that the public IP address of the S3 gateway-enabled host is 110.220.110.220).
+- The S3 gateway can be accessed over the Internet using `http://110.220.110.220:9000` (assuming that the public IP address of the S3 gateway-enabled host is 110.220.110.220).
 
-## Accessing S3 gateway
+## Access S3 gateway
 
 The JuiceFS S3 gateway can be accessed by various clients, desktop applications, web applications, etc. that support the S3 API. Please note the address and port that the S3 gateway listens on when using it.
 
@@ -59,7 +59,7 @@ The JuiceFS S3 gateway can be accessed by various clients, desktop applications,
 The following examples are for using a third-party client to access the S3 gateway running on the local host. In specific scenarios, please adjust the address to access the S3 gateway according to the actual situation.
 :::
 
-### Using the AWS CLI
+### Use the AWS CLI
 
 Download and install the AWS CLI from [https://aws.amazon.com/cli](https://aws.amazon.com/cli), then configure:
 
@@ -71,7 +71,7 @@ Default region name [None]:
 Default output format [None]:
 ```
 
-The program will guide you through adding the new configuration interactively, where `Access Key ID` is the same as `MINIO_ROOT_USER` and `Secret Access Key` is the same as `MINIO_ROOT_PASSWORD`, leave the region name and output format blank.
+The program will guide you interactively to add the new configuration, where `Access Key ID` and `Secret Access Key` are the same as `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD`, respectively. The region name and output format should be left blank.
 
 After that, you can access the JuiceFS storage using the `aws s3` command, for example:
 
@@ -83,7 +83,7 @@ $ aws --endpoint-url http://localhost:9000 s3 ls
 $ aws --endpoint-url http://localhost:9000 s3 ls s3://<bucket>
 ```
 
-### Using the MinIO client
+### Use the MinIO client
 
 First install `mc` by referring to the [MinIO download page](https://min.io/download), then add a new alias:
 
@@ -91,9 +91,9 @@ First install `mc` by referring to the [MinIO download page](https://min.io/down
 $ mc alias set juicefs http://localhost:9000 admin 12345678 --api S3v4
 ```
 
-Following the mc command format, the above command creates a configuration with the alias `juicefs`. In particular, note that the API version must be specified in the command, `-api "s3v4"`.
+Following the mc command format, the above command creates a configuration with the alias `juicefs`. Note that the API version `-api S3v4` must be specified in the command.
 
-Then, you can freely manage the copying, moving, adding and deleting of files and folders between your local disk and JuiceFS storage as well as other cloud storage via the mc client.
+Then, you can freely manage files and folders by copying, moving, adding and deleting between your local disk and JuiceFS storage as well as other cloud storage via the mc client.
 
 ```shell
 $ mc ls juicefs/jfs
@@ -128,21 +128,23 @@ kubectl -n ${NAMESPACE} create secret generic juicefs-secret \
     --from-literal=secret-key=<SECRET_KEY>
 ```
 
-- `name`: The JuiceFS file system name.
-- `metaurl`: Connection URL for metadata engine (e.g. Redis). Read [this document](../reference/how_to_setup_metadata_engine.md) for more information.
-- `storage`: Object storage type, such as `s3`, `gs`, `oss`. Read [this document](../reference/how_to_setup_object_storage.md) for the full supported list.
+Here we have:
+
+- `name`: name of the JuiceFS file system.
+- `metaurl`: URL of the metadata engine (e.g. Redis). Read [this document](../reference/how_to_setup_metadata_engine.md) for more information.
+- `storage`: Object storage type, such as `s3`, `gs`, `oss`. Read [this document](../reference/how_to_setup_object_storage.md) to find all supported object storages.
 - `bucket`: Bucket URL. Read [this document](../reference/how_to_setup_object_storage.md) to learn how to setup different object storage.
 - `access-key`: Access key of object storage. Read [this document](../reference/how_to_setup_object_storage.md) for more information.
 - `secret-key`: Secret key of object storage. Read [this document](../reference/how_to_setup_object_storage.md) for more information.
 
-Then download the S3 gateway [deployment YAML](https://github.com/juicedata/juicefs/blob/main/deploy/juicefs-s3-gateway.yaml) and create the `Deployment` and `Service` resources via `kubectl`. The following points require special attention:
+Then download the S3 gateway [deployment YAML](https://github.com/juicedata/juicefs/blob/main/deploy/juicefs-s3-gateway.yaml) and create the `Deployment` and `Service` resources with `kubectl`. The following points require special attention:
 
 - Please replace `${NAMESPACE}` in the following command with the Kubernetes namespace of the actual S3 gateway deployment, which defaults to `kube-system`.
-- The `replicas` for `Deployment` defaults to 1, please adjust as appropriate.
-- The latest version of `juicedata/juicefs-csi-driver` image is used by default, which already integrates the latest version of JuiceFS client, please check [here](https://github.com/juicedata/juicefs-csi-driver/releases) for the specific integrated JuiceFS client version.
+- The `replicas` for `Deployment` defaults to 1. Please adjust as needed.
+- The latest version of `juicedata/juicefs-csi-driver` image is used by default, which has already integrated the latest version of JuiceFS client. Please check [here](https://github.com/juicedata/juicefs-csi-driver/releases) for the specific integrated JuiceFS client version.
 - The `initContainers` of `Deployment` will first try to format the JuiceFS file system, if you have already formatted it in advance, this step will not affect the existing JuiceFS file system.
-- The default port number that the S3 gateway listens on is 9000
-- The [startup options](../reference/command_reference.md#juicefs-gateway) of S3 gateway are the default values, please adjust them according to your actual needs.
+  - The default port number that the S3 gateway listens on is 9000
+- The [startup options](../reference/command_reference.md#juicefs-gateway) of S3 gateway will use default values if not specified.
 - The value of `MINIO_ROOT_USER` environment variable is `access-key` in Secret, and the value of `MINIO_ROOT_PASSWORD` environment variable is `secret-key` in Secret.
 
 ```shell
@@ -197,7 +199,7 @@ There are some differences between the various versions of Ingress. For more usa
 
 1. Prepare a YAML file
 
-   Create a configuration file, for example: `values.yaml`, copy and complete the following configuration information. Among them, the `secret` part is the information related to the JuiceFS file system, you can refer to [JuiceFS Quick Start Guide](../getting-started/for_local.md) for more information.
+   Create a configuration file, for example: `values.yaml`. Copy and fill in the following configuration information. Among them, the `secret` part is the information related to the JuiceFS file system, and you can refer to [JuiceFS Quick Start Guide](../getting-started/for_local.md) for more information.
 
    ```yaml
    secret:
@@ -209,7 +211,7 @@ There are some differences between the various versions of Ingress. For more usa
      bucket: "<bucket>"
    ```
 
-   If you want to deploy Ingress, add these in `values.yaml`:
+   If you want to deploy Ingress, add the following snippet into `values.yaml`:
 
    ```yaml
    ingress:
@@ -218,7 +220,7 @@ There are some differences between the various versions of Ingress. For more usa
 
 2. Deploy
 
-   Execute the following three commands in sequence to deploy the JuiceFS S3 gateway via Helm (note that the following example is deployed to the `kube-system` namespace).
+   Execute the following three commands in sequence to deploy the JuiceFS S3 gateway with Helm (note that the following example is deployed to the `kube-system` namespace).
 
    ```sh
    helm repo add juicefs-s3-gateway https://juicedata.github.io/charts/
@@ -250,14 +252,14 @@ Please see the ["Monitoring"](../administration/monitoring.md) documentation to
 
 ## Use a full-featured S3 gateway
 
-If you need to use some advanced features of the MinIO S3 gateway, you can pull [the gateway branch of this repository](https://github.com/juicedata/minio/tree/gateway) and compile MinIO yourself. This branch is developed based on the [MinIO RELEASE.2022-03-05T06-32-39Z](https://github.com/minio/minio/tree/RELEASE.2022-03-05T06-32-39Z) version and adds JuiceFS gateway support, which supports full functionality of MinIO gateways such as [multi-user management](https://docs.min.io/docs/minio-multi-user-quickstart-guide.html) while using JuiceFS as a backend.
+If you need to use some advanced features of the MinIO S3 gateway, you can pull [the gateway branch of this repository](https://github.com/juicedata/minio/tree/gateway) and compile MinIO yourself. This branch is developed based on [MinIO RELEASE.2022-03-05T06-32-39Z](https://github.com/minio/minio/tree/RELEASE.2022-03-05T06-32-39Z) with JuiceFS gateway supported, which supports full functionality of MinIO gateways such as [multi-user management](https://docs.min.io/docs/minio-multi-user-quickstart-guide.html) while using JuiceFS as a backend.
 
 ### Compile
 
 :::tip
 This branch relies on a newer version of JuiceFS. Please refer to the [`go.mod`](https://github.com/juicedata/minio/blob/gateway/go.mod) file for the specific JuiceFS version.
 
-Similar to [manually compile JuiceFS client](../getting-started/installation.md#manually-compiling), you need to install some dependencies in advance to compile S3 gateway normally.
+Similar to [manually compiling JuiceFS client](../getting-started/installation.md#manually-compiling), you need to install some dependencies in advance to compile S3 gateway.
 :::
 
 ```shell
@@ -279,6 +281,6 @@ $ export MINIO_ROOT_PASSWORD=12345678
 $ ./minio gateway juicefs --console-address ':59001' redis://localhost:6379
 ```
 
-The port number of the S3 gateway console is explicitly specified here as 59001. If not specified, a port will be randomly selected. According to the command line prompt, open the [http://127.0.0.1:59001](http://127.0.0.1:59001) address in the browser to access the console, as shown in the following figure:
+The port number of the S3 gateway console is explicitly specified here as 59001. If not specified, a port will be randomly assigned. According to the command line prompt, open the address [http://127.0.0.1:59001](http://127.0.0.1:59001) in the browser to access the console, as shown in the following snapshot:
 
 ![](../images/s3-gateway-console.png)
diff --git a/docs/en/getting-started/for_local.md b/docs/en/getting-started/for_local.md
index 42fbe478..071dbc7d 100644
--- a/docs/en/getting-started/for_local.md
+++ b/docs/en/getting-started/for_local.md
@@ -26,7 +26,7 @@ juicefs format [command options] META-URL NAME
 
 As you can see, 3 types of information are required to format a file system.
 
-1. **[command options]**: used to set up the storage medium for the file system; **local disk** will be used by default, and default path is `"$HOME/.juicefs/local"` or `"/var/jfs"`.
+1. **[command options]**: used to set up the storage medium for the file system; **local disk** will be used by default, and default path is `"$HOME/.juicefs/local"`, `"/var/jfs"` or `"C:/jfs/local"`.
 2. **META-URL**: used to set up the metadata engine, usually a URL or the file path of a database.
 3. **NAME**: the name of the file system.
 
diff --git a/docs/en/reference/how_to_setup_object_storage.md b/docs/en/reference/how_to_setup_object_storage.md
index 2e57ebeb..7a84cb03 100644
--- a/docs/en/reference/how_to_setup_object_storage.md
+++ b/docs/en/reference/how_to_setup_object_storage.md
@@ -56,7 +56,7 @@ Some cloud computing platforms also distinguish between internal and public netw
 
 JuiceFS also provides flexible support for this object storage service that distinguishes between internal and public addresses. For scenarios where the same file system is shared, the object storage is accessed through internal Endpoint on the servers that meet the criteria, and other computers are accessed through public Endpoint, which can be used as follows:
 
-- **When creating a file system**: It is recommended to use internal Endpoint address for `--bucket` 
+- **When creating a file system**: It is recommended to use internal Endpoint address for `--bucket`
 - **When mounting a file system**: For clients that do not satisfy the internal line, you can specify a public Endpoint address to `--bucket`.
 
 Creating a file system using an internal Endpoint ensures better performance and lower latency, and for clients that cannot be accessed through an internal address, you can specify a public Endpoint to mount with the option `--bucket`.
@@ -215,6 +215,20 @@ As you can see, there is no need to include authentication information in the co
 
 ## Azure Blob Storage
 
+To use Azure Blob Storage as data storage of JuiceFS, please [check the documentation](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage) to learn how to view the storage account name and access key, which correspond to the values ​​of the `--access-key` and `--secret-key` options, respectively.
+
+The `--bucket` option is set in the format `https://<container>.<endpoint>`, please replace `<container>` with the name of the actual blob container and `<endpoint>` with `blob.core.windows.net` (Azure Global) or `blob.core.chinacloudapi.cn` (Azure China). For example:
+
+```bash
+juicefs format \
+    --storage wasb \
+    --bucket https://<container>.<endpoint> \
+    --access-key <storage-account-name> \
+    --secret-key <storage-account-access-key> \
+    ... \
+    myjfs
+```
+
 In addition to providing authorization information through the options `--access-key` and `--secret-key`, you could also create a [connection string](https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string) and set the environment variable `AZURE_STORAGE_CONNECTION_STRING`. For example:
 
 ```bash
diff --git a/docs/en/release_notes.md b/docs/en/release_notes.md
index 33656605..1ee9f644 100644
--- a/docs/en/release_notes.md
+++ b/docs/en/release_notes.md
@@ -6,14 +6,14 @@ For all versions, please see [GitHub Releases](https://github.com/juicedata/juic
 
 ## Upgrade to JuiceFS v1.0.0 Beta3
 
-JuiceFS client has only one binary file, so when upgrading, you only need to replace the old version with the new one. 
+JuiceFS client has only one binary file, so you only need to replace the old version with the new one when upgrading JuiceFS. 
 
 ### SQL: Update table schema to support encoding other than UTF-8
 
-JuiceFS v1.0.0 Beta3 changed the table schema to support encoding other than UTF-8. For existing file systems, we need to upgrade the table schema to support that. It's recommanded to upgrade all clients first, then update the table schema.
+JuiceFS v1.0.0 Beta3 has changed the table schema to support encoding other than UTF-8. For existing file systems, you need to upgrade the table schema manually to support that. It's recommended to upgrade all clients first and then the table schema.
 
 :::note
-Table schema upgrades are not mandatory and are only required if you need to use non-UTF-8 characters. In addition, metadatabase performance may degrade when upgrading SQL table schemas, affecting running services.
+Table schema upgrades are optional, and they are required only if you need to use non-UTF-8 characters. In addition, database performance may degrade when upgrading SQL table schemas, affecting running services.
 :::
 
 #### MySQL/MariaDB
@@ -36,8 +36,8 @@ alter table jfs_symlink
 
 #### SQLite
 
-Since SQLite does not support modifying columns, you can migrate them by `dump` and `load` commands, refer to: [JuiceFS Metadata Backup and Recovery](administration/metadata_dump_load.md) for details.
+SQLite does not support modifying columns, but you can migrate columns by `dump` and `load` commands, refer to [JuiceFS Metadata Backup and Recovery](administration/metadata_dump_load.md) for details.
 
 ### New session management format
 
-JuiceFS v1.0.0 Beta3 uses a new session management format, previous versions of clients will not be able to see the sessions generated by v1.0.0 Beta3 clients via `juicefs status` or `juicefs destroy`, new versions of clients can see all sessions.
+JuiceFS v1.0.0 Beta3 uses a new session management format. The previous versions of clients cannot see the sessions generated by v1.0.0 Beta3 clients via `juicefs status` or `juicefs destroy`, whereas the new versions are able to see all the sessions.
diff --git a/docs/zh_cn/getting-started/for_local.md b/docs/zh_cn/getting-started/for_local.md
index 68f28e30..bfc23011 100644
--- a/docs/zh_cn/getting-started/for_local.md
+++ b/docs/zh_cn/getting-started/for_local.md
@@ -26,7 +26,7 @@ juicefs format [command options] META-URL NAME
 
 可见，格式化文件系统需要提供 3 种信息：
 
-1. **[command options]**：设定文件系统的存储介质，留空则**默认使用本地磁盘**作为存储介质，路径为 `"$HOME/.juicefs/local"` 或 `"/var/jfs"`；
+1. **[command options]**：设定文件系统的存储介质，留空则**默认使用本地磁盘**作为存储介质，路径为 `"$HOME/.juicefs/local"`，`"/var/jfs"` 或 `"C:/jfs/local"`；
 2. **META-URL**：用来设置元数据存储，即数据库相关的信息，通常是数据库的 URL 或文件路径；
 3. **NAME**：是文件系统的名称。
 
diff --git a/docs/zh_cn/reference/how_to_setup_object_storage.md b/docs/zh_cn/reference/how_to_setup_object_storage.md
index b0a1ccd2..9b9ff8a6 100644
--- a/docs/zh_cn/reference/how_to_setup_object_storage.md
+++ b/docs/zh_cn/reference/how_to_setup_object_storage.md
@@ -220,7 +220,21 @@ $ juicefs format \
 
 ## Azure Blob 存储
 
-使用 Azure Blob 作为 JuiceFS 的数据存储，除了使用 `--access-key` 和 `--secret-key` 选项之外，你也可以使用 [连接字符串](https://docs.microsoft.com/zh-cn/azure/storage/common/storage-configure-connection-string) 并通过 `AZURE_STORAGE_CONNECTION_STRING` 环境变量进行设定。例如：
+使用 Azure Blob 存储作为 JuiceFS 的数据存储，请先 [查看文档](https://docs.microsoft.com/zh-cn/azure/storage/common/storage-account-keys-manage) 了解如何查看存储帐户的名称和密钥，它们分别对应 `--access-key` 和 `--secret-key` 选项的值。
+
+`--bucket` 选项的设置格式为 `https://<container>.<endpoint>`，请将其中的 `<container>` 替换为实际的 Blob 容器的名称，将 `<endpoint>` 替换为 `blob.core.windows.net`（Azure 全球）或 `blob.core.chinacloudapi.cn`（Azure 中国）。例如：
+
+```bash
+juicefs format \
+    --storage wasb \
+    --bucket https://<container>.<endpoint> \
+    --access-key <storage-account-name> \
+    --secret-key <storage-account-access-key> \
+    ... \
+    myjfs
+```
+
+除了使用 `--access-key` 和 `--secret-key` 选项之外，你也可以使用 [连接字符串](https://docs.microsoft.com/zh-cn/azure/storage/common/storage-configure-connection-string) 并通过 `AZURE_STORAGE_CONNECTION_STRING` 环境变量进行设定。例如：
 
 ```bash
 # Use connection string
@@ -233,7 +247,7 @@ $ juicefs format \
 ```
 
 :::note 注意
-Azure China 用户，`EndpointSuffix` 值为 `core.chinacloudapi.cn`。
+对于 Azure 中国用户，`EndpointSuffix` 的值为 `core.chinacloudapi.cn`。
 :::
 
 ## Backblaze B2
@@ -306,7 +320,7 @@ $ juicefs format \
 
 使用 Scaleway 对象存储作为 JuiceFS 数据存储，请先 [查看文档](https://www.scaleway.com/en/docs/generate-api-keys) 了解如何创建  `Access key` 和 `Secret key`。
 
-`--bucket` 选项的设置格式为 `https://<bucket>.s3.<region>.scw.cloud`，请将其中的  `<region>`  替换成实际的区域代码，例如：荷兰阿姆斯特丹的区域代码是 `nl-ams`。[点此查看](https://www.scaleway.com/en/docs/object-storage-feature/#-Core-Concepts) 所有可用的区域代码。
+`--bucket` 选项的设置格式为 `https://<bucket>.s3.<region>.scw.cloud`，请将其中的 `<region>` 替换成实际的区域代码，例如：荷兰阿姆斯特丹的区域代码是 `nl-ams`。[点此查看](https://www.scaleway.com/en/docs/object-storage-feature/#-Core-Concepts) 所有可用的区域代码。
 
 ```bash
 $ juicefs format \
@@ -320,7 +334,7 @@ $ juicefs format \
 
 使用 DigitalOcean Spaces 作为 JuiceFS 数据存储，请先 [查看文档](https://www.digitalocean.com/community/tutorials/how-to-create-a-digitalocean-space-and-api-key) 了解如何创建  `Access key` 和 `Secret key`。
 
-`--bucket` 选项的设置格式为 `https://<space-name>.<region>.digitaloceanspaces.com`，请将其中的  `<region>`  替换成实际的区域代码，例如：`nyc3`。[点此查看](https://www.digitalocean.com/docs/spaces/#regional-availability) 所有可用的区域代码。
+`--bucket` 选项的设置格式为 `https://<space-name>.<region>.digitaloceanspaces.com`，请将其中的 `<region>` 替换成实际的区域代码，例如：`nyc3`。[点此查看](https://www.digitalocean.com/docs/spaces/#regional-availability) 所有可用的区域代码。
 
 ```bash
 $ juicefs format \
diff --git a/pkg/chunk/cached_store.go b/pkg/chunk/cached_store.go
index c779b544..24726338 100644
--- a/pkg/chunk/cached_store.go
+++ b/pkg/chunk/cached_store.go
@@ -441,8 +441,7 @@ func (store *cachedStore) upload(key string, block *Page, c *wChunk) error {
 	n, err := store.compressor.Compress(buf.Data, block.Data)
 	block.Release()
 	if err != nil {
-		logger.Fatalf("Compress block key %s: %s", key, err)
-		return err
+		return fmt.Errorf("Compress block key %s: %s", key, err)
 	}
 	buf.Data = buf.Data[:n]
 
@@ -488,7 +487,7 @@ func (c *wChunk) upload(indx int) {
 			}
 		}
 		if off != blen {
-			logger.Fatalf("block length does not match: %v != %v", off, blen)
+			panic(fmt.Sprintf("block length does not match: %v != %v", off, blen))
 		}
 		if c.store.conf.Writeback {
 			stagingPath, err := c.store.bcache.stage(key, block.Data, c.store.shouldCache(blen))
@@ -534,7 +533,7 @@ func (c *wChunk) Len() int {
 
 func (c *wChunk) FlushTo(offset int) error {
 	if offset < c.uploaded {
-		logger.Fatalf("Invalid offset: %d < %d", offset, c.uploaded)
+		panic(fmt.Sprintf("Invalid offset: %d < %d", offset, c.uploaded))
 	}
 	for i, block := range c.pages {
 		start := i * c.store.conf.BlockSize
diff --git a/pkg/meta/base.go b/pkg/meta/base.go
index fe0f25a1..5031ef1e 100644
--- a/pkg/meta/base.go
+++ b/pkg/meta/base.go
@@ -28,6 +28,7 @@ import (
 	"time"
 
 	"github.com/juicedata/juicefs/pkg/utils"
+	"github.com/juicedata/juicefs/pkg/version"
 )
 
 const (
@@ -69,6 +70,8 @@ type engine interface {
 	doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst Ino, nameDst string, flags uint32, inode *Ino, attr *Attr) syscall.Errno
 	doSetXattr(ctx Context, inode Ino, name string, value []byte, flags uint32) syscall.Errno
 	doRemoveXattr(ctx Context, inode Ino, name string) syscall.Errno
+
+	GetSession(sid uint64, detail bool) (*Session, error)
 }
 
 type baseMeta struct {
@@ -82,6 +85,7 @@ type baseMeta struct {
 	of           *openfiles
 	removedFiles map[Ino]bool
 	compacting   map[uint64]bool
+	maxDeleting  chan struct{}
 	symlinks     *sync.Map
 	msgCallbacks *msgCallbacks
 	newSpace     int64
@@ -110,6 +114,7 @@ func newBaseMeta(conf *Config) baseMeta {
 		of:           newOpenFiles(conf.OpenCache),
 		removedFiles: make(map[Ino]bool),
 		compacting:   make(map[uint64]bool),
+		maxDeleting:  make(chan struct{}, 100),
 		symlinks:     &sync.Map{},
 		msgCallbacks: &msgCallbacks{
 			callbacks: make(map[uint32]MsgCallback),
@@ -166,6 +171,7 @@ func (m *baseMeta) Load(checkVersion bool) (*Format, error) {
 func (m *baseMeta) NewSession() error {
 	go m.refreshUsage()
 	if m.conf.ReadOnly {
+		logger.Infof("Create read-only session OK with version: %s", version.Version())
 		return nil
 	}
 
@@ -183,7 +189,7 @@ func (m *baseMeta) NewSession() error {
 	if err = m.en.doNewSession(data); err != nil {
 		return fmt.Errorf("create session: %s", err)
 	}
-	logger.Infof("create session %d OK", m.sid)
+	logger.Infof("Create session %d OK with version: %s", m.sid, version.Version())
 
 	go m.refreshSession()
 	if !m.conf.NoBGJob {
@@ -229,7 +235,12 @@ func (m *baseMeta) CleanStaleSessions() {
 		return
 	}
 	for _, sid := range sids {
-		logger.Infof("clean up stale session %d: %s", sid, m.en.doCleanStaleSession(sid))
+		s, err := m.en.GetSession(sid, false)
+		if err != nil {
+			logger.Warnf("Get session info %d: %s", sid, err)
+			s = &Session{Sid: sid}
+		}
+		logger.Infof("clean up stale session %d %+v: %s", sid, s.SessionInfo, m.en.doCleanStaleSession(sid))
 	}
 }
 
@@ -780,9 +791,7 @@ func (m *baseMeta) Close(ctx Context, inode Ino) syscall.Errno {
 		defer m.Unlock()
 		if m.removedFiles[inode] {
 			delete(m.removedFiles, inode)
-			go func() {
-				_ = m.en.doDeleteSustainedInode(m.sid, inode)
-			}()
+			_ = m.en.doDeleteSustainedInode(m.sid, inode)
 		}
 	}
 	return 0
@@ -843,12 +852,24 @@ func (m *baseMeta) fileDeleted(opened bool, inode Ino, length uint64) {
 		m.removedFiles[inode] = true
 		m.Unlock()
 	} else {
-		go m.en.doDeleteFileData(inode, length)
+		m.tryDeleteFileData(inode, length)
+	}
+}
+
+func (m *baseMeta) tryDeleteFileData(inode Ino, length uint64) {
+	select {
+	case m.maxDeleting <- struct{}{}:
+		go func() {
+			m.en.doDeleteFileData(inode, length)
+			<-m.maxDeleting
+		}()
+	default:
+		// will be cleanup later
 	}
 }
 
 func (m *baseMeta) deleteSlice(chunkid uint64, size uint32) {
-	if err := m.newMsg(DeleteChunk, chunkid, size); err == nil {
+	if err := m.newMsg(DeleteChunk, chunkid, size); err == nil || strings.Contains(err.Error(), "NoSuchKey") || strings.Contains(err.Error(), "not found") {
 		if err = m.en.doDeleteSlice(chunkid, size); err != nil {
 			logger.Errorf("delete slice %d: %s", chunkid, err)
 		}
diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go
index 3627e018..361851ea 100644
--- a/pkg/meta/interface.go
+++ b/pkg/meta/interface.go
@@ -249,7 +249,7 @@ type Meta interface {
 	// CloseSession does cleanup and close the session.
 	CloseSession() error
 	// GetSession retrieves information of session with sid
-	GetSession(sid uint64) (*Session, error)
+	GetSession(sid uint64, detail bool) (*Session, error)
 	// ListSessions returns all client sessions.
 	ListSessions() ([]*Session, error)
 	// CleanStaleSessions cleans up sessions not active for more than 5 minutes
@@ -389,7 +389,7 @@ func NewClient(uri string, conf *Config) Meta {
 	}
 	m, err := f(driver, uri[p+3:], conf)
 	if err != nil {
-		logger.Fatalf("Meta is not available: %s", err)
+		logger.Fatalf("Meta %s is not available: %s", uri, err)
 	}
 	return m
 }
diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go
index 46fabf1e..a1eeb6e8 100644
--- a/pkg/meta/redis.go
+++ b/pkg/meta/redis.go
@@ -228,7 +228,7 @@ func (m *redisMeta) Init(format Format, force bool) error {
 		var old Format
 		err = json.Unmarshal(body, &old)
 		if err != nil {
-			logger.Fatalf("existing format is broken: %s", err)
+			return fmt.Errorf("existing format is broken: %s", err)
 		}
 		if err = format.update(&old, force); err != nil {
 			return err
@@ -237,7 +237,7 @@ func (m *redisMeta) Init(format Format, force bool) error {
 
 	data, err := json.MarshalIndent(format, "", "")
 	if err != nil {
-		logger.Fatalf("json: %s", err)
+		return fmt.Errorf("json: %s", err)
 	}
 	ts := time.Now().Unix()
 	attr := &Attr{
@@ -408,7 +408,7 @@ func (m *redisMeta) getSession(sid string, detail bool) (*Session, error) {
 	return &s, nil
 }
 
-func (m *redisMeta) GetSession(sid uint64) (*Session, error) {
+func (m *redisMeta) GetSession(sid uint64, detail bool) (*Session, error) {
 	var legacy bool
 	key := strconv.FormatUint(sid, 10)
 	score, err := m.rdb.ZScore(Background, m.allSessions(), key).Result()
@@ -422,7 +422,7 @@ func (m *redisMeta) GetSession(sid uint64) (*Session, error) {
 	if err != nil {
 		return nil, err
 	}
-	s, err := m.getSession(key, true)
+	s, err := m.getSession(key, detail)
 	if err != nil {
 		return nil, err
 	}
@@ -1874,7 +1874,7 @@ func (m *redisMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
 	})
 	if err == nil {
 		m.updateStats(newSpace, -1)
-		go m.doDeleteFileData(inode, attr.Length)
+		m.tryDeleteFileData(inode, attr.Length)
 	}
 	return err
 }
diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go
index 322344f2..2fd41b97 100644
--- a/pkg/meta/sql.go
+++ b/pkg/meta/sql.go
@@ -218,30 +218,30 @@ func (m *dbMeta) Name() string {
 }
 
 func (m *dbMeta) doDeleteSlice(chunkid uint64, size uint32) error {
-	return m.txn(func(ses *xorm.Session) error {
-		_, err := ses.Exec("delete from jfs_chunk_ref where chunkid=?", chunkid)
+	return m.txn(func(s *xorm.Session) error {
+		_, err := s.Exec("delete from jfs_chunk_ref where chunkid=?", chunkid)
 		return err
 	})
 }
 
 func (m *dbMeta) Init(format Format, force bool) error {
 	if err := m.db.Sync2(new(setting), new(counter)); err != nil {
-		logger.Fatalf("create table setting, counter: %s", err)
+		return fmt.Errorf("create table setting, counter: %s", err)
 	}
 	if err := m.db.Sync2(new(edge)); err != nil && !strings.Contains(err.Error(), "Duplicate entry") {
-		logger.Fatalf("create table edge: %s", err)
+		return fmt.Errorf("create table edge: %s", err)
 	}
 	if err := m.db.Sync2(new(node), new(symlink), new(xattr)); err != nil {
-		logger.Fatalf("create table node, symlink, xattr: %s", err)
+		return fmt.Errorf("create table node, symlink, xattr: %s", err)
 	}
 	if err := m.db.Sync2(new(chunk), new(chunkRef), new(delslices)); err != nil {
-		logger.Fatalf("create table chunk, chunk_ref, delslices: %s", err)
+		return fmt.Errorf("create table chunk, chunk_ref, delslices: %s", err)
 	}
 	if err := m.db.Sync2(new(session2), new(sustained), new(delfile)); err != nil {
-		logger.Fatalf("create table session2, sustaind, delfile: %s", err)
+		return fmt.Errorf("create table session2, sustaind, delfile: %s", err)
 	}
 	if err := m.db.Sync2(new(flock), new(plock)); err != nil {
-		logger.Fatalf("create table flock, plock: %s", err)
+		return fmt.Errorf("create table flock, plock: %s", err)
 	}
 
 	var s = setting{Name: "format"}
@@ -279,7 +279,7 @@ func (m *dbMeta) Init(format Format, force bool) error {
 	}
 	return m.txn(func(s *xorm.Session) error {
 		if format.TrashDays > 0 {
-			ok2, err := s.Get(&node{Inode: TrashInode})
+			ok2, err := s.ForUpdate().Get(&node{Inode: TrashInode})
 			if err != nil {
 				return err
 			}
@@ -420,12 +420,12 @@ func (m *dbMeta) getSession(row interface{}, detail bool) (*Session, error) {
 	return &s, nil
 }
 
-func (m *dbMeta) GetSession(sid uint64) (*Session, error) {
+func (m *dbMeta) GetSession(sid uint64, detail bool) (*Session, error) {
 	row := session2{Sid: sid}
 	if ok, err := m.db.Get(&row); err != nil {
 		return nil, err
 	} else if ok {
-		return m.getSession(&row, true)
+		return m.getSession(&row, detail)
 	}
 	if ok, err := m.db.IsTableExist(&session{}); err != nil {
 		return nil, err
@@ -434,7 +434,7 @@ func (m *dbMeta) GetSession(sid uint64) (*Session, error) {
 		if ok, err := m.db.Get(&row); err != nil {
 			return nil, err
 		} else if ok {
-			return m.getSession(&row, true)
+			return m.getSession(&row, detail)
 		}
 	}
 	return nil, fmt.Errorf("session not found: %d", sid)
@@ -486,7 +486,7 @@ func (m *dbMeta) incrCounter(name string, value int64) (int64, error) {
 	var v int64
 	err := m.txn(func(s *xorm.Session) error {
 		var c = counter{Name: name}
-		ok, err := s.Get(&c)
+		ok, err := s.ForUpdate().Get(&c)
 		if err != nil {
 			return err
 		}
@@ -509,7 +509,7 @@ func (m *dbMeta) setIfSmall(name string, value, diff int64) (bool, error) {
 	err := m.txn(func(s *xorm.Session) error {
 		changed = false
 		c := counter{Name: name}
-		ok, err := s.Get(&c)
+		ok, err := s.ForUpdate().Get(&c)
 		if err != nil {
 			return err
 		}
@@ -582,7 +582,6 @@ func (m *dbMeta) txn(f func(s *xorm.Session) error) error {
 	var err error
 	for i := 0; i < 50; i++ {
 		_, err = m.db.Transaction(func(s *xorm.Session) (interface{}, error) {
-			s.ForUpdate()
 			return nil, f(s)
 		})
 		if m.shouldRetry(err) {
@@ -645,12 +644,12 @@ func (m *dbMeta) flushStats() {
 }
 
 func (m *dbMeta) doLookup(ctx Context, parent Ino, name string, inode *Ino, attr *Attr) syscall.Errno {
-	dbSession := m.db.Table(&edge{})
+	db := m.db.Table(&edge{})
 	if attr != nil {
-		dbSession = dbSession.Join("INNER", &node{}, "jfs_edge.inode=jfs_node.inode")
+		db = db.Join("INNER", &node{}, "jfs_edge.inode=jfs_node.inode")
 	}
 	nn := namedNode{node: node{Parent: parent}, Name: []byte(name)}
-	exist, err := dbSession.Select("*").Get(&nn)
+	exist, err := db.Select("*").Get(&nn)
 	if err != nil {
 		return errno(err)
 	}
@@ -703,7 +702,7 @@ func (m *dbMeta) SetAttr(ctx Context, inode Ino, set uint16, sugidclearmode uint
 	defer func() { m.of.InvalidateChunk(inode, 0xFFFFFFFE) }()
 	return errno(m.txn(func(s *xorm.Session) error {
 		var cur = node{Inode: inode}
-		ok, err := s.Get(&cur)
+		ok, err := s.ForUpdate().Get(&cur)
 		if err != nil {
 			return err
 		}
@@ -804,7 +803,7 @@ func (m *dbMeta) Truncate(ctx Context, inode Ino, flags uint8, length uint64, at
 	var newSpace int64
 	err := m.txn(func(s *xorm.Session) error {
 		var n = node{Inode: inode}
-		ok, err := s.Get(&n)
+		ok, err := s.ForUpdate().Get(&n)
 		if err != nil {
 			return err
 		}
@@ -829,7 +828,7 @@ func (m *dbMeta) Truncate(ctx Context, inode Ino, flags uint8, length uint64, at
 			right, left = left, right
 		}
 		if right/ChunkSize-left/ChunkSize > 1 {
-			rows, err := s.Where("inode = ? AND indx > ? AND indx < ?", inode, left/ChunkSize, right/ChunkSize).Cols("indx").Rows(&c)
+			rows, err := s.Where("inode = ? AND indx > ? AND indx < ?", inode, left/ChunkSize, right/ChunkSize).Cols("indx").ForUpdate().Rows(&c)
 			if err != nil {
 				return err
 			}
@@ -903,7 +902,7 @@ func (m *dbMeta) Fallocate(ctx Context, inode Ino, mode uint8, off uint64, size
 	var newSpace int64
 	err := m.txn(func(s *xorm.Session) error {
 		var n = node{Inode: inode}
-		ok, err := s.Get(&n)
+		ok, err := s.ForUpdate().Get(&n)
 		if err != nil {
 			return err
 		}
@@ -1006,7 +1005,7 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode
 
 	err = m.txn(func(s *xorm.Session) error {
 		var pn = node{Inode: parent}
-		ok, err := s.Get(&pn)
+		ok, err := s.ForUpdate().Get(&pn)
 		if err != nil {
 			return err
 		}
@@ -1017,7 +1016,7 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode
 			return syscall.ENOTDIR
 		}
 		var e = edge{Parent: parent, Name: []byte(name)}
-		ok, err = s.Get(&e)
+		ok, err = s.ForUpdate().Get(&e)
 		if err != nil {
 			return err
 		}
@@ -1033,7 +1032,7 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode
 		if foundIno != 0 {
 			if _type == TypeFile || _type == TypeDirectory {
 				foundNode := node{Inode: foundIno}
-				ok, err = s.Get(&foundNode)
+				ok, err = s.ForUpdate().Get(&foundNode)
 				if err != nil {
 					return err
 				} else if ok {
@@ -1103,7 +1102,7 @@ func (m *dbMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno {
 	var opened bool
 	err := m.txn(func(s *xorm.Session) error {
 		var pn = node{Inode: parent}
-		ok, err := s.Get(&pn)
+		ok, err := s.ForUpdate().Get(&pn)
 		if err != nil {
 			return err
 		}
@@ -1114,7 +1113,7 @@ func (m *dbMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno {
 			return syscall.ENOTDIR
 		}
 		var e = edge{Parent: parent, Name: []byte(name)}
-		ok, err = s.Get(&e)
+		ok, err = s.ForUpdate().Get(&e)
 		if err != nil {
 			return err
 		}
@@ -1134,7 +1133,7 @@ func (m *dbMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno {
 		}
 
 		n = node{Inode: e.Inode}
-		ok, err = s.Get(&n)
+		ok, err = s.ForUpdate().Get(&n)
 		if err != nil {
 			return err
 		}
@@ -1235,7 +1234,7 @@ func (m *dbMeta) doRmdir(ctx Context, parent Ino, name string) syscall.Errno {
 	}
 	err := m.txn(func(s *xorm.Session) error {
 		var pn = node{Inode: parent}
-		ok, err := s.Get(&pn)
+		ok, err := s.ForUpdate().Get(&pn)
 		if err != nil {
 			return err
 		}
@@ -1246,7 +1245,7 @@ func (m *dbMeta) doRmdir(ctx Context, parent Ino, name string) syscall.Errno {
 			return syscall.ENOTDIR
 		}
 		var e = edge{Parent: parent, Name: []byte(name)}
-		ok, err = s.Get(&e)
+		ok, err = s.ForUpdate().Get(&e)
 		if err != nil {
 			return err
 		}
@@ -1264,7 +1263,7 @@ func (m *dbMeta) doRmdir(ctx Context, parent Ino, name string) syscall.Errno {
 		if e.Type != TypeDirectory {
 			return syscall.ENOTDIR
 		}
-		exist, err := s.Exist(&edge{Parent: e.Inode})
+		exist, err := s.ForUpdate().Exist(&edge{Parent: e.Inode})
 		if err != nil {
 			return err
 		}
@@ -1272,7 +1271,7 @@ func (m *dbMeta) doRmdir(ctx Context, parent Ino, name string) syscall.Errno {
 			return syscall.ENOTEMPTY
 		}
 		var n = node{Inode: e.Inode}
-		ok, err = s.Get(&n)
+		ok, err = s.ForUpdate().Get(&n)
 		if err != nil {
 			return err
 		}
@@ -1335,7 +1334,7 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst
 	var newSpace, newInode int64
 	err := m.txn(func(s *xorm.Session) error {
 		var se = edge{Parent: parentSrc, Name: []byte(nameSrc)}
-		ok, err := s.Get(&se)
+		ok, err := s.ForUpdate().Get(&se)
 		if err != nil {
 			return err
 		}
@@ -1357,7 +1356,7 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst
 			return nil
 		}
 		var spn = node{Inode: parentSrc}
-		ok, err = s.Get(&spn)
+		ok, err = s.ForUpdate().Get(&spn)
 		if err != nil {
 			return err
 		}
@@ -1368,7 +1367,7 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst
 			return syscall.ENOTDIR
 		}
 		var dpn = node{Inode: parentDst}
-		ok, err = s.Get(&dpn)
+		ok, err = s.ForUpdate().Get(&dpn)
 		if err != nil {
 			return err
 		}
@@ -1379,7 +1378,7 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst
 			return syscall.ENOTDIR
 		}
 		var sn = node{Inode: se.Inode}
-		ok, err = s.Get(&sn)
+		ok, err = s.ForUpdate().Get(&sn)
 		if err != nil {
 			return err
 		}
@@ -1388,7 +1387,7 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst
 		}
 
 		var de = edge{Parent: parentDst, Name: []byte(nameDst)}
-		ok, err = s.Get(&de)
+		ok, err = s.ForUpdate().Get(&de)
 		if err != nil {
 			return err
 		}
@@ -1409,7 +1408,7 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst
 				return syscall.EEXIST
 			}
 			dino = de.Inode
-			ok, err := s.Get(&dn)
+			ok, err := s.ForUpdate().Get(&dn)
 			if err != nil {
 				return err
 			}
@@ -1427,7 +1426,7 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst
 				}
 			} else {
 				if de.Type == TypeDirectory {
-					exist, err := s.Exist(&edge{Parent: de.Inode})
+					exist, err := s.ForUpdate().Exist(&edge{Parent: de.Inode})
 					if err != nil {
 						return err
 					}
@@ -1583,7 +1582,7 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst
 func (m *dbMeta) doLink(ctx Context, inode, parent Ino, name string, attr *Attr) syscall.Errno {
 	return errno(m.txn(func(s *xorm.Session) error {
 		var pn = node{Inode: parent}
-		ok, err := s.Get(&pn)
+		ok, err := s.ForUpdate().Get(&pn)
 		if err != nil {
 			return err
 		}
@@ -1594,7 +1593,7 @@ func (m *dbMeta) doLink(ctx Context, inode, parent Ino, name string, attr *Attr)
 			return syscall.ENOTDIR
 		}
 		var e = edge{Parent: parent, Name: []byte(name)}
-		ok, err = s.Get(&e)
+		ok, err = s.ForUpdate().Get(&e)
 		if err != nil {
 			return err
 		}
@@ -1603,7 +1602,7 @@ func (m *dbMeta) doLink(ctx Context, inode, parent Ino, name string, attr *Attr)
 		}
 
 		var n = node{Inode: inode}
-		ok, err = s.Get(&n)
+		ok, err = s.ForUpdate().Get(&n)
 		if err != nil {
 			return err
 		}
@@ -1773,7 +1772,7 @@ func (m *dbMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
 	var n = node{Inode: inode}
 	var newSpace int64
 	err := m.txn(func(s *xorm.Session) error {
-		ok, err := s.Get(&n)
+		ok, err := s.ForUpdate().Get(&n)
 		if err != nil {
 			return err
 		}
@@ -1793,7 +1792,7 @@ func (m *dbMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
 	})
 	if err == nil {
 		m.updateStats(newSpace, -1)
-		go m.doDeleteFileData(inode, n.Length)
+		m.tryDeleteFileData(inode, n.Length)
 	}
 	return err
 }
@@ -1838,7 +1837,7 @@ func (m *dbMeta) Write(ctx Context, inode Ino, indx uint32, off uint32, slice Sl
 	var needCompact bool
 	err := m.txn(func(s *xorm.Session) error {
 		var n = node{Inode: inode}
-		ok, err := s.Get(&n)
+		ok, err := s.ForUpdate().Get(&n)
 		if err != nil {
 			return err
 		}
@@ -1861,7 +1860,7 @@ func (m *dbMeta) Write(ctx Context, inode Ino, indx uint32, off uint32, slice Sl
 		n.Ctime = now
 
 		var ck chunk
-		ok, err = s.Where("Inode = ? and Indx = ?", inode, indx).Get(&ck)
+		ok, err = s.ForUpdate().Where("Inode = ? and Indx = ?", inode, indx).Get(&ck)
 		if err != nil {
 			return err
 		}
@@ -1904,11 +1903,11 @@ func (m *dbMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino, off
 	defer func() { m.of.InvalidateChunk(fout, 0xFFFFFFFF) }()
 	err := m.txn(func(s *xorm.Session) error {
 		var nin, nout = node{Inode: fin}, node{Inode: fout}
-		ok, err := s.Get(&nin)
+		ok, err := s.ForUpdate().Get(&nin)
 		if err != nil {
 			return err
 		}
-		ok2, err2 := s.Get(&nout)
+		ok2, err2 := s.ForUpdate().Get(&nout)
 		if err2 != nil {
 			return err2
 		}
@@ -1942,7 +1941,7 @@ func (m *dbMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino, off
 		nout.Ctime = now
 
 		var c chunk
-		rows, err := s.Where("inode = ? AND indx >= ? AND indx <= ?", fin, offIn/ChunkSize, (offIn+size)/ChunkSize).Rows(&c)
+		rows, err := s.Where("inode = ? AND indx >= ? AND indx <= ?", fin, offIn/ChunkSize, (offIn+size)/ChunkSize).ForUpdate().Rows(&c)
 		if err != nil {
 			return err
 		}
@@ -2059,8 +2058,8 @@ func (m *dbMeta) doCleanupSlices() {
 func (m *dbMeta) deleteChunk(inode Ino, indx uint32) error {
 	var c chunk
 	var ss []*slice
-	err := m.txn(func(ses *xorm.Session) error {
-		ok, err := ses.Where("inode = ? AND indx = ?", inode, indx).Get(&c)
+	err := m.txn(func(s *xorm.Session) error {
+		ok, err := s.ForUpdate().Where("inode = ? AND indx = ?", inode, indx).Get(&c)
 		if err != nil {
 			return err
 		}
@@ -2068,14 +2067,14 @@ func (m *dbMeta) deleteChunk(inode Ino, indx uint32) error {
 			return nil
 		}
 		ss = readSliceBuf(c.Slices)
-		for _, s := range ss {
-			_, err = ses.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? AND size=?", s.chunkid, s.size)
+		for _, sc := range ss {
+			_, err = s.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? AND size=?", sc.chunkid, sc.size)
 			if err != nil {
 				return err
 			}
 		}
 		c.Slices = nil
-		n, err := ses.Where("inode = ? AND indx = ?", inode, indx).Delete(&c)
+		n, err := s.Where("inode = ? AND indx = ?", inode, indx).Delete(&c)
 		if err == nil && n == 0 {
 			err = fmt.Errorf("chunk %d:%d changed, try restarting transaction", inode, indx)
 		}
@@ -2137,7 +2136,7 @@ func (m *dbMeta) doCleanupDelayedSlices(edge int64, limit int) (int, error) {
 	for _, ds := range result {
 		if err = m.txn(func(ses *xorm.Session) error {
 			ds := delslices{Chunkid: ds.Chunkid}
-			if ok, e := ses.Get(&ds); e != nil {
+			if ok, e := ses.ForUpdate().Get(&ds); e != nil {
 				return e
 			} else if !ok {
 				return nil
@@ -2225,9 +2224,9 @@ func (m *dbMeta) compactChunk(inode Ino, indx uint32, force bool) {
 			buf = append(buf, m.encodeDelayedSlice(s.chunkid, s.size)...)
 		}
 	}
-	err = m.txn(func(ses *xorm.Session) error {
+	err = m.txn(func(s *xorm.Session) error {
 		var c2 = chunk{Inode: inode}
-		_, err := ses.Where("indx=?", indx).Get(&c2)
+		_, err := s.ForUpdate().Where("indx=?", indx).Get(&c2)
 		if err != nil {
 			return err
 		}
@@ -2237,20 +2236,20 @@ func (m *dbMeta) compactChunk(inode Ino, indx uint32, force bool) {
 		}
 
 		c2.Slices = append(append(c2.Slices[:skipped*sliceBytes], marshalSlice(pos, chunkid, size, 0, size)...), c2.Slices[len(c.Slices):]...)
-		if _, err := ses.Where("Inode = ? AND indx = ?", inode, indx).Update(c2); err != nil {
+		if _, err := s.Where("Inode = ? AND indx = ?", inode, indx).Update(c2); err != nil {
 			return err
 		}
 		// create the key to tracking it
-		if err = mustInsert(ses, chunkRef{chunkid, size, 1}); err != nil {
+		if err = mustInsert(s, chunkRef{chunkid, size, 1}); err != nil {
 			return err
 		}
 		if trash {
-			if err = mustInsert(ses, &delslices{chunkid, time.Now().Unix(), buf}); err != nil {
+			if err = mustInsert(s, &delslices{chunkid, time.Now().Unix(), buf}); err != nil {
 				return err
 			}
 		} else {
-			for _, s := range ss {
-				if _, err := ses.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? and size=?", s.chunkid, s.size); err != nil {
+			for _, s_ := range ss {
+				if _, err := s.Exec("update jfs_chunk_ref set refs=refs-1 where chunkid=? and size=?", s_.chunkid, s_.size); err != nil {
 					return err
 				}
 			}
@@ -2480,9 +2479,9 @@ func (m *dbMeta) doRemoveXattr(ctx Context, inode Ino, name string) syscall.Errn
 
 func (m *dbMeta) dumpEntry(inode Ino, typ uint8) (*DumpedEntry, error) {
 	e := &DumpedEntry{}
-	return e, m.txn(func(s *xorm.Session) error {
+	return e, m.txn(func(db *xorm.Session) error {
 		n := &node{Inode: inode}
-		ok, err := m.db.Get(n)
+		ok, err := db.Get(n)
 		if err != nil {
 			return err
 		}
@@ -2496,7 +2495,7 @@ func (m *dbMeta) dumpEntry(inode Ino, typ uint8) (*DumpedEntry, error) {
 		e.Attr.Inode = inode
 
 		var rows []xattr
-		if err = m.db.Find(&rows, &xattr{Inode: inode}); err != nil {
+		if err = db.Find(&rows, &xattr{Inode: inode}); err != nil {
 			return err
 		}
 		if len(rows) > 0 {
@@ -2511,7 +2510,7 @@ func (m *dbMeta) dumpEntry(inode Ino, typ uint8) (*DumpedEntry, error) {
 		if attr.Typ == TypeFile {
 			for indx := uint32(0); uint64(indx)*ChunkSize < attr.Length; indx++ {
 				c := &chunk{Inode: inode, Indx: indx}
-				if ok, err = m.db.Get(c); err != nil {
+				if ok, err = db.Get(c); err != nil {
 					return err
 				}
 				if !ok {
@@ -2527,7 +2526,7 @@ func (m *dbMeta) dumpEntry(inode Ino, typ uint8) (*DumpedEntry, error) {
 			}
 		} else if attr.Typ == TypeSymlink {
 			l := &symlink{Inode: inode}
-			ok, err = m.db.Get(l)
+			ok, err = db.Get(l)
 			if err != nil {
 				return err
 			}
diff --git a/pkg/meta/sql_lock.go b/pkg/meta/sql_lock.go
index 79113f60..4195f73e 100644
--- a/pkg/meta/sql_lock.go
+++ b/pkg/meta/sql_lock.go
@@ -38,13 +38,13 @@ func (m *dbMeta) Flock(ctx Context, inode Ino, owner_ uint64, ltype uint32, bloc
 	var err syscall.Errno
 	for {
 		err = errno(m.txn(func(s *xorm.Session) error {
-			if exists, err := s.Get(&node{Inode: inode}); err != nil || !exists {
+			if exists, err := s.ForUpdate().Get(&node{Inode: inode}); err != nil || !exists {
 				if err == nil && !exists {
 					err = syscall.ENOENT
 				}
 				return err
 			}
-			rows, err := s.Rows(&flock{Inode: inode})
+			rows, err := s.ForUpdate().Rows(&flock{Inode: inode})
 			if err != nil {
 				return err
 			}
@@ -159,7 +159,7 @@ func (m *dbMeta) Setlk(ctx Context, inode Ino, owner_ uint64, block bool, ltype
 	owner := int64(owner_)
 	for {
 		err = errno(m.txn(func(s *xorm.Session) error {
-			if exists, err := s.Get(&node{Inode: inode}); err != nil || !exists {
+			if exists, err := s.ForUpdate().Get(&node{Inode: inode}); err != nil || !exists {
 				if err == nil && !exists {
 					err = syscall.ENOENT
 				}
@@ -167,7 +167,7 @@ func (m *dbMeta) Setlk(ctx Context, inode Ino, owner_ uint64, block bool, ltype
 			}
 			if ltype == F_UNLCK {
 				var l = plock{Inode: inode, Owner: owner, Sid: m.sid}
-				ok, err := m.db.Get(&l)
+				ok, err := s.ForUpdate().Get(&l)
 				if err != nil {
 					return err
 				}
@@ -186,7 +186,7 @@ func (m *dbMeta) Setlk(ctx Context, inode Ino, owner_ uint64, block bool, ltype
 				}
 				return err
 			}
-			rows, err := s.Rows(&plock{Inode: inode})
+			rows, err := s.ForUpdate().Rows(&plock{Inode: inode})
 			if err != nil {
 				return err
 			}
diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go
index d8bf6b4d..c21e754a 100644
--- a/pkg/meta/tkv.go
+++ b/pkg/meta/tkv.go
@@ -120,7 +120,7 @@ func (m *kvMeta) keyLen(args ...interface{}) int {
 		case string:
 			c += len(a)
 		default:
-			logger.Fatalf("invalid type %T, value %v", a, a)
+			panic(fmt.Sprintf("invalid type %T, value %v", a, a))
 		}
 	}
 	return c
@@ -141,7 +141,7 @@ func (m *kvMeta) fmtKey(args ...interface{}) []byte {
 		case string:
 			b.Put([]byte(a))
 		default:
-			logger.Fatalf("invalid type %T, value %v", a, a)
+			panic(fmt.Sprintf("invalid type %T, value %v", a, a))
 		}
 	}
 	return b.Bytes()
@@ -342,7 +342,7 @@ func (m *kvMeta) Init(format Format, force bool) error {
 
 	data, err := json.MarshalIndent(format, "", "")
 	if err != nil {
-		logger.Fatalf("json: %s", err)
+		return fmt.Errorf("json: %s", err)
 	}
 
 	m.fmt = format
@@ -559,7 +559,7 @@ func (m *kvMeta) getSession(sid uint64, detail bool) (*Session, error) {
 	return &s, nil
 }
 
-func (m *kvMeta) GetSession(sid uint64) (*Session, error) {
+func (m *kvMeta) GetSession(sid uint64, detail bool) (*Session, error) {
 	var legacy bool
 	value, err := m.get(m.sessionKey(sid))
 	if err == nil && value == nil {
@@ -572,7 +572,7 @@ func (m *kvMeta) GetSession(sid uint64) (*Session, error) {
 	if value == nil {
 		return nil, fmt.Errorf("session not found: %d", sid)
 	}
-	s, err := m.getSession(sid, true)
+	s, err := m.getSession(sid, detail)
 	if err != nil {
 		return nil, err
 	}
@@ -1575,7 +1575,7 @@ func (m *kvMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
 	})
 	if err == nil {
 		m.updateStats(newSpace, -1)
-		go m.doDeleteFileData(inode, attr.Length)
+		m.tryDeleteFileData(inode, attr.Length)
 	}
 	return err
 }
diff --git a/pkg/object/azure.go b/pkg/object/azure.go
index ed04098f..00e56595 100644
--- a/pkg/object/azure.go
+++ b/pkg/object/azure.go
@@ -85,7 +85,7 @@ func (b *wasb) Copy(dst, src string) error {
 func (b *wasb) Delete(key string) error {
 	_, err := b.container.NewBlockBlobClient(key).Delete(ctx, &azblob.DeleteBlobOptions{})
 	if err != nil && strings.Contains(err.Error(), string(azblob.StorageErrorCodeBlobNotFound)) {
-		return nil
+		err = nil
 	}
 	return err
 }
diff --git a/pkg/object/bos.go b/pkg/object/bos.go
index bd04edae..4bc624b2 100644
--- a/pkg/object/bos.go
+++ b/pkg/object/bos.go
@@ -101,7 +101,11 @@ func (q *bosclient) Copy(dst, src string) error {
 }
 
 func (q *bosclient) Delete(key string) error {
-	return q.c.DeleteObject(q.bucket, key)
+	err := q.c.DeleteObject(q.bucket, key)
+	if err != nil && strings.Contains(err.Error(), "NoSuchKey") {
+		err = nil
+	}
+	return err
 }
 
 func (q *bosclient) List(prefix, marker string, limit int64) ([]Object, error) {
diff --git a/pkg/object/minio.go b/pkg/object/minio.go
index 8c627e2f..45fd5ed5 100644
--- a/pkg/object/minio.go
+++ b/pkg/object/minio.go
@@ -72,7 +72,7 @@ func newMinio(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 	ses.Handlers.Build.PushFront(disableSha256Func)
 
 	if len(uri.Path) < 2 {
-		return nil, fmt.Errorf("no bucket name provided in %s: %s", endpoint, err)
+		return nil, fmt.Errorf("no bucket name provided in %s", endpoint)
 	}
 	bucket := uri.Path[1:]
 	if strings.Contains(bucket, "/") && strings.HasPrefix(bucket, "minio/") {
diff --git a/pkg/object/s3.go b/pkg/object/s3.go
index aa4c8cb2..9821cd32 100644
--- a/pkg/object/s3.go
+++ b/pkg/object/s3.go
@@ -158,6 +158,9 @@ func (s *s3client) Delete(key string) error {
 		Key:    &key,
 	}
 	_, err := s.s3.DeleteObject(&param)
+	if err != nil && strings.Contains(err.Error(), "NoSuckKey") {
+		err = nil
+	}
 	return err
 }
 
@@ -176,6 +179,9 @@ func (s *s3client) List(prefix, marker string, limit int64) ([]Object, error) {
 	objs := make([]Object, n)
 	for i := 0; i < n; i++ {
 		o := resp.Contents[i]
+		if !strings.HasPrefix(*o.Key, prefix) || *o.Key < marker {
+			return nil, fmt.Errorf("found invalid key %s from List, prefix: %s, marker: %s", *o.Key, prefix, marker)
+		}
 		objs[i] = &obj{
 			*o.Key,
 			*o.Size,
diff --git a/pkg/object/sharding.go b/pkg/object/sharding.go
index aa20a67c..0ba70393 100644
--- a/pkg/object/sharding.go
+++ b/pkg/object/sharding.go
@@ -94,7 +94,9 @@ func ListAll(store ObjectStorage, prefix, marker string) (<-chan Object, error)
 			for _, obj := range objs {
 				key := obj.Key()
 				if !first && key <= lastkey {
-					logger.Fatalf("The keys are out of order: marker %q, last %q current %q", marker, lastkey, key)
+					logger.Errorf("The keys are out of order: marker %q, last %q current %q", marker, lastkey, key)
+					out <- nil
+					return
 				}
 				lastkey = key
 				// logger.Debugf("found key: %s", key)
diff --git a/pkg/object/swift.go b/pkg/object/swift.go
index 9d58ba71..f4981504 100644
--- a/pkg/object/swift.go
+++ b/pkg/object/swift.go
@@ -20,12 +20,14 @@
 package object
 
 import (
+	"errors"
 	"fmt"
-	"github.com/juicedata/juicefs/pkg/utils"
-	"github.com/ncw/swift"
 	"io"
 	"net/url"
 	"strings"
+
+	"github.com/juicedata/juicefs/pkg/utils"
+	"github.com/ncw/swift"
 )
 
 type swiftOSS struct {
@@ -65,7 +67,11 @@ func (s *swiftOSS) Put(key string, in io.Reader) error {
 }
 
 func (s *swiftOSS) Delete(key string) error {
-	return s.conn.ObjectDelete(s.container, key)
+	err := s.conn.ObjectDelete(s.container, key)
+	if err != nil && errors.Is(err, swift.ObjectNotFound) {
+		err = nil
+	}
+	return err
 }
 
 func (s *swiftOSS) List(prefix, marker string, limit int64) ([]Object, error) {
diff --git a/pkg/sync/sync.go b/pkg/sync/sync.go
index 7c37a6bc..b4f2d389 100644
--- a/pkg/sync/sync.go
+++ b/pkg/sync/sync.go
@@ -129,7 +129,9 @@ func ListAll(store object.ObjectStorage, start, end string) (<-chan object.Objec
 			for _, obj := range objs {
 				key := obj.Key()
 				if !first && key <= lastkey {
-					logger.Fatalf("The keys are out of order: marker %q, last %q current %q", marker, lastkey, key)
+					logger.Errorf("The keys are out of order: marker %q, last %q current %q", marker, lastkey, key)
+					out <- nil
+					return
 				}
 				if end != "" && key > end {
 					break END
@@ -612,7 +614,7 @@ func deleteFromDst(tasks chan<- object.Object, dstobj object.Object, config *Con
 	return false
 }
 
-func producer(tasks chan<- object.Object, src, dst object.ObjectStorage, config *Config) {
+func startProducer(tasks chan<- object.Object, src, dst object.ObjectStorage, config *Config) error {
 	start, end := config.Start, config.End
 	logger.Infof("Syncing from %s to %s", src, dst)
 	if start != "" {
@@ -625,12 +627,12 @@ func producer(tasks chan<- object.Object, src, dst object.ObjectStorage, config
 
 	srckeys, err := ListAll(src, start, end)
 	if err != nil {
-		logger.Fatal(err)
+		return fmt.Errorf("list %s: %s", src, err)
 	}
 
 	dstkeys, err := ListAll(dst, start, end)
 	if err != nil {
-		logger.Fatal(err)
+		return fmt.Errorf("list %s: %s", dst, err)
 	}
 	if len(config.Exclude) > 0 {
 		rules := parseIncludeRules(os.Args)
@@ -642,7 +644,11 @@ func producer(tasks chan<- object.Object, src, dst object.ObjectStorage, config
 		srckeys = filter(srckeys, rules)
 		dstkeys = filter(dstkeys, rules)
 	}
+	go produce(tasks, src, dst, srckeys, dstkeys, config)
+	return nil
+}
 
+func produce(tasks chan<- object.Object, src, dst object.ObjectStorage, srckeys, dstkeys <-chan object.Object, config *Config) {
 	defer close(tasks)
 	var dstobj object.Object
 	for obj := range srckeys {
@@ -799,7 +805,7 @@ func matchKey(rules []rule, key string) bool {
 			suffix := suffixForPattern(prefix+s, rule.pattern)
 			ok, err := path.Match(rule.pattern, suffix)
 			if err != nil {
-				logger.Fatalf("match %s with %s: %v", rule.pattern, suffix, err)
+				panic(fmt.Sprintf("match %s with %s: %v", rule.pattern, suffix, err))
 			}
 			if ok {
 				if rule.include {
@@ -856,7 +862,10 @@ func Sync(src, dst object.ObjectStorage, config *Config) error {
 	}
 
 	if config.Manager == "" {
-		go producer(tasks, src, dst, config)
+		err := startProducer(tasks, src, dst, config)
+		if err != nil {
+			return err
+		}
 		if len(config.Workers) > 0 {
 			addr, err := startManager(tasks)
 			if err != nil {
diff --git a/pkg/vfs/backup.go b/pkg/vfs/backup.go
index f542bd29..77021416 100644
--- a/pkg/vfs/backup.go
+++ b/pkg/vfs/backup.go
@@ -104,6 +104,10 @@ func cleanupBackups(blob object.ObjectStorage, now time.Time) {
 	}
 	var objs []string
 	for o := range ch {
+		if o == nil {
+			logger.Warnf("list failed, skip cleanup")
+			return
+		}
 		if !o.IsDir() {
 			objs = append(objs, o.Key())
 		}
diff --git a/pkg/winfsp/winfs.go b/pkg/winfsp/winfs.go
index e4458f98..daac2efa 100644
--- a/pkg/winfsp/winfs.go
+++ b/pkg/winfsp/winfs.go
@@ -564,7 +564,7 @@ func (j *juice) Releasedir(path string, fh uint64) (e int) {
 	return
 }
 
-func Serve(v *vfs.VFS, fuseOpt string, fileCacheTo float64, asRoot bool, delayClose int) error {
+func Serve(v *vfs.VFS, fuseOpt string, fileCacheTo float64, asRoot bool, delayClose int) {
 	var jfs juice
 	conf := v.Conf
 	jfs.conf = conf
@@ -593,5 +593,4 @@ func Serve(v *vfs.VFS, fuseOpt string, fileCacheTo float64, asRoot bool, delayCl
 	host.SetCapReaddirPlus(true)
 	logger.Debugf("mount point: %s, options: %s", conf.Meta.MountPoint, options)
 	_ = host.Mount(conf.Meta.MountPoint, []string{"-o", options})
-	return nil
 }
diff --git a/sdk/java/libjfs/main.go b/sdk/java/libjfs/main.go
index c0b68b60..4f96c445 100644
--- a/sdk/java/libjfs/main.go
+++ b/sdk/java/libjfs/main.go
@@ -436,7 +436,8 @@ func jfs_init(cname, jsonConf, user, group, superuser, supergroup *C.char) uintp
 			return format, err
 		})
 		if err != nil {
-			logger.Fatalf("object storage: %s", err)
+			logger.Errorf("object storage: %s", err)
+			return nil
 		}
 		logger.Infof("Data use %s", blob)
 
diff --git a/sdk/java/pom.xml b/sdk/java/pom.xml
index 2499e19a..a26cdc3e 100644
--- a/sdk/java/pom.xml
+++ b/sdk/java/pom.xml
@@ -37,11 +37,19 @@
 					<relocations>
 						<relocation>
 							<pattern>org.objectweb.asm</pattern>
-							<shadedPattern>juice.org.objectweb.asm</shadedPattern>
+							<shadedPattern>io.juicefs.shaded.org.objectweb.asm</shadedPattern>
 						</relocation>
 						<relocation>
 							<pattern>com.beust</pattern>
-							<shadedPattern>juice.com.beust</shadedPattern>
+							<shadedPattern>io.juicefs.shaded.com.beust</shadedPattern>
+						</relocation>
+						<relocation>
+							<pattern>org.json</pattern>
+							<shadedPattern>io.juicefs.shaded.org.json</shadedPattern>
+						</relocation>
+						<relocation>
+							<pattern>org.javassist</pattern>
+							<shadedPattern>io.juicefs.shaded.org.javassist</shadedPattern>
 						</relocation>
 					</relocations>
 				</configuration>


Test patch:
diff --git a/pkg/meta/redis_test.go b/pkg/meta/redis_test.go
index 05df136a..736a43ca 100644
--- a/pkg/meta/redis_test.go
+++ b/pkg/meta/redis_test.go
@@ -697,11 +697,10 @@ func testLocks(t *testing.T, m Meta) {
 			time.Sleep(time.Millisecond)
 			count--
 			if count > 0 {
-				logger.Fatalf("count should be be zero but got %d", count)
+				t.Fatalf("count should be be zero but got %d", count)
 			}
 			if st := m.Setlk(ctx, inode, uint64(i), false, syscall.F_UNLCK, 0, 0xFFFF, uint32(i)); st != 0 {
-				logger.Fatalf("plock unlock: %s", st)
-				err = st
+				t.Fatalf("plock unlock: %s", st)
 			}
 		}(i)
 	}
@@ -1110,7 +1109,7 @@ func testCloseSession(t *testing.T, m Meta) {
 	case *kvMeta:
 		sid = m.sid
 	}
-	s, err := m.GetSession(sid)
+	s, err := m.GetSession(sid, true)
 	if err != nil {
 		t.Fatalf("get session: %s", err)
 	} else {
@@ -1121,7 +1120,7 @@ func testCloseSession(t *testing.T, m Meta) {
 	if err = m.CloseSession(); err != nil {
 		t.Fatalf("close session: %s", err)
 	}
-	if _, err = m.GetSession(sid); err == nil {
+	if _, err = m.GetSession(sid, true); err == nil {
 		t.Fatalf("get a deleted session: %s", err)
 	}
 	switch m := m.(type) {