Copy Blob shares the source's page ranges and append-block list with the copy: writing to the copy changes the source, and writing to a restored blob changes its snapshot
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 78/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- typescript
調査の方向性
Issueで挙げられている startCopyFromURL と copyFromURL の経路について、まず src/blob/persistence/LokiBlobMetadataStore.ts を確認してください。次に createSnapshot を調べ、ページ範囲と確定済みブロック配列をどのようにコピーするかを確認してください。スクリプトにある page blob、snapshot の復元、append blob のケースを再現してください。コピーした blob への書き込みで元の blob や snapshot が変化しなくなり、各 blob が期待どおりのデータと ETag の動作を返せば完了です。
索引モデルが issue の本文から書いたものです。
説明
Which service(blob, file, queue, table) does this issue concern?
Blob
Which version of the Azurite was used?
3.37.0 (also seen on 3.29.0)
Where do you get Azurite? (npm, DockerHub, NuGet, Visual Studio Code Extension)
Docker image mcr.microsoft.com/azure-storage/azurite:3.37.0 (same digest as latest)
What's the Node.js version?
v22.23.2 (the one in the image)
What problem was encountered?
After Copy Blob copies a page blob, the copy and the source share one list of page ranges. A Put
Page on either blob then changes the content of both. The other blob's ETag does not change, so a
client cannot see that its data changed. Append blobs share their list of
committed blocks in the same way.
Three cases (script below):
- Copy, then write the copy.
p1is a 1 KiB page blob that is never written. Copy Blob
p1 -> p2, then Put Page0-511withbonp2. Get Blob onp1now returns 512bbytes
followed by 512 zero bytes, with the same ETag as before. - Restore a snapshot, then write the blob.
p3holdsain0-511. Snapshot Blobp3,
restore the snapshot overp3with Copy Blob (x-ms-copy-source: .../p3?snapshot=<id>), then
Put Page0-511withconp3. The snapshot now readsc. The backup that the restore came
from has been overwritten by the next write to the restored blob. - Append blobs.
a1holdsx. Copy Bloba1 -> a2, Append Blockytoa2, then Append
Blockztoa1. Get Blob ona1returnsxy, notxz: the block appended to the copy is
now part of the source, and the source's ownzis not visible. Before the last append,
x-ms-blob-committed-block-countofa1was already2.
The documentation describes the copy as a new blob that receives the source's data, and a
snapshot as read-only. Copy Blob, remarks:
When you're copying from a page blob, Blob Storage creates a destination page blob of the source
blob's length. Initially, the page blob contains all zeroes. Then the source page ranges are
enumerated, and non-empty ranges are copied.
You can perform a copy operation to promote a snapshot over its base blob, as long as it's in an
online tier (hot, cool, or cold). In this way, you can restore an earlier version of a blob. The
snapshot remains, but its destination is overwritten with a copy that can be both read and
written.
Snapshots provide read-only versions of blobs. After you create a snapshot, you can read, copy,
or delete it, but you can't modify it.
Put Page: "The Put Page
operation writes a range of pages to a page blob." That is, to the blob in the request URL.
Append Block: "The
Append Block operation commits a new block of data to the end of an existing append blob."
Expected: in the first case p1 stays all zeroes; in the second the snapshot keeps a; in the
third a1 reads xz.
Steps to reproduce the issue?
docker run -d -p 10000:10000 mcr.microsoft.com/azure-storage/azurite:3.37.0 azurite-blob --blobHost 0.0.0.0
Then:
B=http://127.0.0.1:10000/devstoreaccount1
# Account SAS derived from Azurite's public default development key, not a cloud credential.
SAS='sv=2023-11-03&ss=b&srt=sco&sp=rwdl&se=2099-01-01&sig=gF53rwiyjUw0Grh2NB8D%2Bw%2FK1kP1kspe6Cj3FCMjg%2F0%3D'
V='x-ms-version: 2023-11-03'
req() { label=$1; shift; printf '%-42s %s\n' "$label" "$(curl -s -o /dev/null -w '%{http_code}' -X PUT -H "$V" -H 'Content-Length: 0' "$@")"; }
page() { printf '%-42s %s\n' "$1" "$(head -c 512 /dev/zero | tr '\0' "$2" | curl -s -o /dev/null -w '%{http_code}' -X PUT -H "$V" \
-H 'x-ms-page-write: update' -H 'x-ms-range: bytes=0-511' --data-binary @- "$3")"; }
first() { printf '%-42s%s\n' "$1" "$(curl -s -H "$V" "$2" | od -An -c -N4)"; }
req 'create container' "$B/pages?restype=container&$SAS"
# A. p1: a 1 KiB page blob that is never written. p2: a copy of p1. Write 'b' to p2.
req 'create page blob p1 (1024 bytes)' -H 'x-ms-blob-type: PageBlob' -H 'x-ms-blob-content-length: 1024' "$B/pages/p1?$SAS"
req 'Copy Blob p1 -> p2' -H "x-ms-copy-source: $B/pages/p1" "$B/pages/p2?$SAS"
page 'Put Page p2 bytes 0-511 = b' b "$B/pages/p2?comp=page&$SAS"
first 'first 4 bytes of p1:' "$B/pages/p1?$SAS"
# B. p3 holds 'a'. Snapshot it, restore the snapshot over p3 with Copy Blob, write 'c' to p3.
req 'create page blob p3 (1024 bytes)' -H 'x-ms-blob-type: PageBlob' -H 'x-ms-blob-content-length: 1024' "$B/pages/p3?$SAS"
page 'Put Page p3 bytes 0-511 = a' a "$B/pages/p3?comp=page&$SAS"
SNAP=$(curl -s -o /dev/null -D - -X PUT -H "$V" -H 'Content-Length: 0' "$B/pages/p3?comp=snapshot&$SAS" \
| tr -d '\r' | awk -F': ' 'tolower($1)=="x-ms-snapshot" {print $2}')
echo "Snapshot Blob p3: x-ms-snapshot $SNAP"
first 'first 4 bytes of the snapshot:' "$B/pages/p3?snapshot=$SNAP&$SAS"
req 'Copy Blob p3?snapshot -> p3 (restore)' -H "x-ms-copy-source: $B/pages/p3?snapshot=$SNAP" "$B/pages/p3?$SAS"
page 'Put Page p3 bytes 0-511 = c' c "$B/pages/p3?comp=page&$SAS"
first 'first 4 bytes of the snapshot:' "$B/pages/p3?snapshot=$SNAP&$SAS"
# C. Append blobs: a1 holds 'x'. Copy a1 -> a2, append 'y' to a2, then append 'z' to a1.
append() { printf '%-42s %s\n' "Append Block $2 = $1" "$(curl -s -o /dev/null -w '%{http_code}' -X PUT -H "$V" --data-binary "$1" "$B/pages/$2?comp=appendblock&$SAS")"; }
req 'create append blob a1' -H 'x-ms-blob-type: AppendBlob' "$B/pages/a1?$SAS"
append x a1
req 'Copy Blob a1 -> a2' -H "x-ms-copy-source: $B/pages/a1" "$B/pages/a2?$SAS"
append y a2
append z a1
echo "Get Blob a1: $(curl -s -H "$V" "$B/pages/a1?$SAS")"
Output on 3.37.0:
create container 201
create page blob p1 (1024 bytes) 201
Copy Blob p1 -> p2 202
Put Page p2 bytes 0-511 = b 201
first 4 bytes of p1: b b b b
create page blob p3 (1024 bytes) 201
Put Page p3 bytes 0-511 = a 201
Snapshot Blob p3: x-ms-snapshot 2026-11-01T18:24:13.0870000Z
first 4 bytes of the snapshot: a a a a
Copy Blob p3?snapshot -> p3 (restore) 202
Put Page p3 bytes 0-511 = c 201
first 4 bytes of the snapshot: c c c c
create append blob a1 201
Append Block a1 = x 201
Copy Blob a1 -> a2 202
Append Block a2 = y 201
Append Block a1 = z 201
Get Blob a1: xy
Expected: \0 \0 \0 \0 for p1, a a a a for the snapshot at the end, and xz for a1. In our runs the ETag
of p1 was the same before and after the write to p2 (for example "0x1DF07064A5C5EC0" both
times).
Have you found a mitigation/solution?
Mitigation: after copying a page or append blob, do not write to the copy or the source.
Potential cause. LokiBlobMetadataStore.startCopyFromURL builds the new blob under the comment
// Deep clone a copied blob but sets pageRangesInOrder: sourceBlob.pageRangesInOrder
(src/blob/persistence/LokiBlobMetadataStore.ts:2017, inserted at :2060); copyFromURL does the
same (:2208, :2253). The blobs collection is created without clone
(:181-183), so the source document and the copy hold the same array object.
uploadPages and clearRange then change that array in place through
PageBlobRangesManager.mergeRange / clearRange, which use splice
(src/blob/handlers/PageBlobRangesManager.ts:74, :113, :168; called from
LokiBlobMetadataStore.ts:2804 and :2869). The next lines of both copy paths do the same with
committedBlocksInOrder: sourceBlob.committedBlocksInOrder (:2026, :2217), and
appendBlock pushes onto that array (doc.committedBlocksInOrder.push(block), :2494-2495).
createSnapshot already copies both arrays (.slice(), :1145-1153), which is why a snapshot on
its own is safe until a copy is made from it.
Suggested fix. Copy both arrays in both copy paths, as createSnapshot does, for example
pageRangesInOrder: sourceBlob.pageRangesInOrder === undefined ? undefined : sourceBlob.pageRangesInOrder.slice()
and the same for committedBlocksInOrder. mergeRange and clearRange splice new range objects
into the array rather than changing the existing ones, so a shallow copy looks enough.
- 主要言語
- TypeScript
- スター
- 2.3k
- フォーク
- 400
- 平均マージ
- 4日 9時間
- マージ済み PR(30日)
- 23
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートあり
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Azure/Azurite のほかの issue
-
Container-scoped Blob Batch runs subrequests on another container whose name starts with the scope container's name対応中かも @compiler-segfault が 1 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 75/100
Azure/Azurite#2872 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
Azure/Azurite#2865 · リアクション 10 件 ·
メンテナーはふだん 1 日以内に返信
-
Get Blob with Range bytes=-1 returns 500対応中かも @hippi345 が 21 日前に担当しました。 オープン
難易度 3/5 半日 初心者へのやさしさ 62/100
メンテナーはふだん 1 日以内に返信
-
Acquiring a blob lease without a duration causes later requests to return 500対応中かも @hippi345 が 21 日前に担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 65/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
難易度 1/5 1〜3時間 初心者へのやさしさ 86/100
ranxianglei/billion-context#5893 ·
メンテナーはふだん 1 日以内に返信
-
stringify escapes leading digits in pseudo-class arguments (:nth-child(1) becomes :nth-child(\31 ))オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 83/100
メンテナーはふだん 1 日以内に返信
-
enhancement security severity:low
難易度 2/5 1〜3時間 初心者へのやさしさ 83/100
Battle-Cattle/BCUK-Bot-4#959 ·
メンテナーはふだん 1 日以内に返信
-
[Bug]: Claude account rows no longer show the email (since #8055), so "Account 1, 2, 3" cannot be told apart対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンaccount-pool bug gui
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
lidge-jun/opencodex#8286 · コメント 2 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信