Skip to content

Commit d655f5c

Browse files
committed
copy_file_range optimization tidy up
1 parent 90f8126 commit d655f5c

4 files changed

Lines changed: 39 additions & 19 deletions

File tree

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,19 @@ mcd_root/
105105

106106
是否在备份时临时关闭自动保存
107107

108-
### copy_on_write
108+
### enable_copy_file_range
109109

110110
默认值: `false`
111111

112-
使用某些文件系统的写时拷贝(一种增量备份)
112+
使用 `os.copy_file_range` 进行文件的复制
113+
114+
在某些文件系统中,它会使用基于写时复制(copy-on-write)的 reflink 技术,从而极大地提升复制速度
115+
116+
需求:
117+
118+
- Linux 平台
119+
- Python >= 3.8
120+
- 选项 `backup_format``plain`
113121

114122
### ignored_files
115123

README_en.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,19 @@ Default: `true`
105105

106106
If turn off auto save when making backup or not
107107

108-
### copy_on_write
108+
### enable_copy_file_range
109109

110110
Default: `false`
111111

112-
Useing copy_on_write in some File system(incremental backup)
112+
Use `os.copy_file_range` for file copying
113+
114+
In some file system, it will use the copy-on-write based reflink technique to greatly accelerate the copy speed
115+
116+
Requirements:
117+
118+
- Linux
119+
- Python >= 3.8
120+
- Option `backup_format` set to `plain`
113121

114122
### ignored_files
115123

quick_backup_multi/__init__.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,27 +82,31 @@ def get_backup_file_name(backup_format: BackupFormat):
8282
return backup_format.get_file_name('backup')
8383

8484

85-
copy_file_range_supported=hasattr(os, "copy_file_range")
86-
COW_COPY_BUFFER_SIZE = 2**30 # 1GB / need int, may overflow, so cannot copy files larger than 2GB in a single pass
85+
COPY_FILE_RANGE_SUPPORTED = hasattr(os, 'copy_file_range')
86+
COPY_FILE_RANGE_BUFFER_SIZE = 2 ** 30 # 1GiB
8787

88-
#copy using "Copy On Write"
89-
def _cpcow(src_path: str, dst_path: str):
90-
if not copy_file_range_supported or not config.copy_on_write:
88+
89+
def copy_file_fast(src_path: str, dst_path: str) -> str:
90+
"""
91+
A ``shutil.copy2`` alternative that uses ``os.copy_file_range`` whenever possible
92+
93+
``os.copy_file_range`` may support copy-on-write, which is much faster than regular copy
94+
"""
95+
if not COPY_FILE_RANGE_SUPPORTED or not config.enable_copy_file_range:
9196
return shutil.copy2(src_path, dst_path)
9297

93-
if os.path.isdir(dst_path):
98+
if os.path.isdir(dst_path): # ref: shutil.copy2
9499
dst_path = os.path.join(dst_path, os.path.basename(src_path))
95100

96101
try:
97-
with open(src_path,'rb') as fsrc, open(dst_path,'wb+') as fdst:
98-
while os.copy_file_range(fsrc.fileno(), fdst.fileno(), COW_COPY_BUFFER_SIZE):
102+
with open(src_path, 'rb') as f_src, open(dst_path, 'wb+') as f_dst:
103+
while os.copy_file_range(f_src.fileno(), f_dst.fileno(), COPY_FILE_RANGE_BUFFER_SIZE):
99104
pass
100-
101105
except Exception as e:
102-
server_inst.logger.warning(str(e) + str(src_path) + "->" + str(dst_path) + ",Retry with other functions")
106+
server_inst.logger.warning('copy_file_range {} -> {} failed ({}), retrying with shutil.copy'.format(src_path, dst_path, e))
103107
shutil.copy(src_path, dst_path)
104108

105-
shutil.copystat(src_path, dst_path) # copy2
109+
shutil.copystat(src_path, dst_path) # ref: shutil.copy2
106110
return dst_path
107111

108112

@@ -126,12 +130,12 @@ def copy_worlds(src: str, dst: str, intent: CopyWorldIntent, *, backup_format: O
126130

127131
server_inst.logger.info('copying {} -> {}'.format(src_path, dst_path))
128132
if os.path.isdir(src_path):
129-
shutil.copytree(src_path, dst_path, ignore=lambda path, files: set(filter(config.is_file_ignored, files)), copy_function=_cpcow)
133+
shutil.copytree(src_path, dst_path, ignore=lambda path, files: set(filter(config.is_file_ignored, files)), copy_function=copy_file_fast)
130134
elif os.path.isfile(src_path):
131135
dst_dir = os.path.dirname(dst_path)
132136
if not os.path.isdir(dst_dir):
133137
os.makedirs(dst_dir)
134-
_cpcow(src_path, dst_path)
138+
copy_file_fast(src_path, dst_path)
135139
else:
136140
server_inst.logger.warning('{} does not exist while copying ({} -> {})'.format(src_path, src_path, dst_path))
137141
elif backup_format in [BackupFormat.tar, BackupFormat.tar_gz, BackupFormat.tar_xz]:

quick_backup_multi/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Dict, Optional
1+
from typing import List, Dict
22

33
from mcdreforged.api.utils.serializer import Serializable
44

@@ -10,7 +10,7 @@ class SlotInfo(Serializable):
1010
class Configuration(Serializable):
1111
size_display: bool = True
1212
turn_off_auto_save: bool = True
13-
copy_on_write: bool = False
13+
enable_copy_file_range: bool = False
1414
ignored_files: List[str] = [
1515
'session.lock'
1616
]

0 commit comments

Comments
 (0)