ranged_mmap/file/
mmap_file_inner.rs

1//! Unsafe lock-free file implementation based on memmap2
2//! 
3//! 基于 memmap2 的 Unsafe 无锁文件实现
4
5use memmap2::MmapMut;
6use std::cell::UnsafeCell;
7use std::fs::OpenOptions;
8use std::path::Path;
9use std::sync::Arc;
10use std::num::NonZeroU64;
11use super::error::{Error, Result};
12
13/// High-performance memory-mapped file (Unsafe lock-free version)
14///
15/// 基于内存映射的高性能文件(Unsafe 无锁版本)
16///
17/// ⚠️ **Warning**: This is an unsafe version. Users must ensure that concurrent writes
18/// do not overlap.
19/// 
20/// ⚠️ **警告**:这是 unsafe 版本,用户需自行保证并发写入的范围不重叠。
21/// 
22/// It is recommended to use [`MmapFile`](super::MmapFile) + [`RangeAllocator`](super::RangeAllocator)
23/// for compile-time safety.
24/// 
25/// 推荐使用 [`MmapFile`](super::MmapFile) + [`RangeAllocator`](super::RangeAllocator) 实现编译期安全。
26///
27/// This file implementation is optimized for concurrent random write scenarios.
28///
29/// 专为并发随机写入场景优化的文件实现。
30///
31/// # Features
32///
33/// - **Zero-copy writes**: Write operations directly modify mapped memory without system calls
34/// - **Lock-free concurrency**: Concurrent writes to different regions require no locking for maximum performance
35/// - **Reference counting**: Can be cloned and shared among multiple workers
36/// - **Manual flushing**: Control when data is synchronized to disk for optimized batch operations
37/// - **Runtime agnostic**: Does not depend on any specific async runtime
38///
39/// # 特性
40///
41/// - **零拷贝写入**:写入操作直接修改映射内存,无需系统调用
42/// - **无锁并发**:不同区域的并发写入无需加锁,极致性能
43/// - **引用计数**:可以克隆并在多个 worker 间共享
44/// - **手动刷盘**:控制何时将数据同步到磁盘,优化批量操作
45/// - **运行时无关**:不依赖特定异步运行时,可用于任何场景
46///
47/// # Limitations
48///
49/// - File size must be specified at creation and cannot be dynamically expanded
50/// - Maximum file size is limited by system virtual memory
51/// - ⚠️ Users must ensure that concurrent writes do not overlap (runtime responsibility)
52///
53/// # 限制
54///
55/// - 创建时必须指定文件大小,不支持动态扩展
56/// - 文件大小上限受系统虚拟内存限制
57/// - ⚠️ 用户需要确保不会并发写入重叠的内存区域(运行时责任)
58///
59/// # Safety Notes
60///
61/// This implementation uses `UnsafeCell` to allow lock-free concurrent writes. As long as:
62/// - Different threads write to non-overlapping memory regions
63/// - No reads occur to the same region during writes
64/// 
65/// It is completely safe. However, these guarantees must be maintained by the user.
66///
67/// # 安全性说明
68///
69/// 这个实现使用 `UnsafeCell` 来允许无锁并发写入。只要:
70/// - 不同线程写入不重叠的内存区域
71/// - 不在写入同时读取同一区域
72/// 
73/// 那么就是完全安全的。但这些保证需要用户自行维护。
74///
75/// # Examples
76///
77/// ```
78/// # use ranged_mmap::{MmapFileInner, Result};
79/// # use tempfile::tempdir;
80/// # fn main() -> Result<()> {
81/// # let dir = tempdir()?;
82/// # let path = dir.path().join("download.bin");
83/// # use std::num::NonZeroU64;
84/// let file = MmapFileInner::create(&path, NonZeroU64::new(1024).unwrap())?;
85///
86/// // ⚠️ Users must ensure concurrent writes do not overlap
87/// // ⚠️ 用户需自行保证不会并发写入重叠区域
88/// let file1 = file.clone();
89/// let file2 = file.clone();
90///
91/// std::thread::scope(|s| {
92///     // Safety: Two threads write to non-overlapping regions [0, 512) and [512, 1024)
93///     // Safety: 两个线程写入不重叠的区域 [0, 512) 和 [512, 1024)
94///     s.spawn(|| unsafe { file1.write_at(0, &[1; 512]) });
95///     s.spawn(|| unsafe { file2.write_at(512, &[2; 512]) });
96/// });
97///
98/// unsafe { file.flush()?; }
99/// # Ok(())
100/// # }
101/// ```
102#[derive(Clone)]
103pub struct MmapFileInner {
104    /// Mutable reference to memory mapping, using UnsafeCell for interior mutability
105    /// 
106    /// 内存映射的可变引用,使用 UnsafeCell 允许内部可变性
107    /// 
108    /// # Safety
109    /// Safe as long as different threads write to non-overlapping regions
110    /// 
111    /// # Safety
112    /// 只要不同线程写入不重叠的区域,就是安全的
113    mmap: Arc<UnsafeCell<MmapMut>>,
114    
115    /// File size in bytes
116    /// 
117    /// 文件大小
118    size: NonZeroU64,
119}
120
121impl MmapFileInner {
122    /// Create a new file and map it to memory
123    ///
124    /// 创建新文件并映射到内存
125    ///
126    /// If the file already exists, it will be truncated. The file will be pre-allocated
127    /// to the specified size.
128    ///
129    /// 如果文件已存在会被截断。文件会被预分配到指定大小。
130    ///
131    /// # Parameters
132    /// - `path`: File path
133    /// - `size`: File size in bytes, must be > 0
134    ///
135    /// # 参数
136    /// - `path`: 文件路径
137    /// - `size`: 文件大小(字节),必须大于 0
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// # use ranged_mmap::{MmapFileInner, Result};
143    /// # use tempfile::tempdir;
144    /// # fn main() -> Result<()> {
145    /// # let dir = tempdir()?;
146    /// # let path = dir.path().join("output.bin");
147    /// # use std::num::NonZeroU64;
148    /// // Create a 10MB file
149    /// // 创建 10MB 的文件
150    /// let file = MmapFileInner::create(&path, NonZeroU64::new(10 * 1024 * 1024).unwrap())?;
151    /// # Ok(())
152    /// # }
153    /// ```
154    ///
155    /// # Errors
156    /// - Returns `InvalidFileSize` error if size is 0
157    /// - Returns corresponding I/O errors if file creation or memory mapping fails
158    ///
159    /// # Errors
160    /// - 如果 size 为 0,返回 `InvalidFileSize` 错误
161    /// - 如果无法创建文件或映射内存,返回相应的 I/O 错误
162    pub fn create(path: impl AsRef<Path>, size: NonZeroU64) -> Result<Self> {
163
164        let path = path.as_ref();
165
166        // Create file and pre-allocate size
167        // 创建文件并预分配大小
168        let file = OpenOptions::new()
169            .read(true)
170            .write(true)
171            .create(true)
172            .truncate(true)
173            .open(path)?;
174
175        file.set_len(size.get())?;
176
177        // Create memory mapping
178        // 创建内存映射
179        let mmap = unsafe { MmapMut::map_mut(&file)? };
180
181        Ok(Self {
182            #[allow(clippy::arc_with_non_send_sync)]
183            mmap: Arc::new(UnsafeCell::new(mmap)),
184            size,
185        })
186    }
187
188    /// Open an existing file and map it to memory
189    ///
190    /// 打开已存在的文件并映射到内存
191    ///
192    /// The file must already exist and have a size > 0.
193    ///
194    /// 文件必须已存在且大小大于 0。
195    ///
196    /// # Parameters
197    /// - `path`: File path
198    ///
199    /// # 参数
200    /// - `path`: 文件路径
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// # use ranged_mmap::{MmapFileInner, Result};
206    /// # use tempfile::tempdir;
207    /// # fn main() -> Result<()> {
208    /// # let dir = tempdir()?;
209    /// # let path = dir.path().join("existing.bin");
210    /// # use std::num::NonZeroU64;
211    /// # // Create file first
212    /// # // 先创建文件
213    /// # let _ = MmapFileInner::create(&path, NonZeroU64::new(1024).unwrap())?;
214    /// let file = MmapFileInner::open(&path)?;
215    /// # Ok(())
216    /// # }
217    /// ```
218    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
219        let path = path.as_ref();
220
221        let file = OpenOptions::new()
222            .read(true)
223            .write(true)
224            .open(path)?;
225
226        let size = match file.metadata()?.len() {
227            0 => return Err(Error::EmptyFile),
228            size => NonZeroU64::new(size).unwrap(),
229        };
230
231        let mmap = unsafe { MmapMut::map_mut(&file)? };
232
233        Ok(Self {
234            #[allow(clippy::arc_with_non_send_sync)]
235            mmap: Arc::new(UnsafeCell::new(mmap)),
236            size,
237        })
238    }
239
240    /// Write data at the specified position (lock-free operation)
241    ///
242    /// 在指定位置写入数据(无锁操作)
243    ///
244    /// This is an extremely fast operation that writes directly to mapped memory
245    /// without requiring any locks.
246    /// 
247    /// 这是一个极快的操作,直接写入映射内存,不需要任何锁。
248    /// 
249    /// # Safety
250    /// 
251    /// The caller must ensure:
252    /// - Different threads do not write to overlapping memory regions concurrently
253    /// - No reads occur to the same region during writes
254    ///
255    /// Violating these constraints leads to data races, which is undefined behavior.
256    /// 
257    /// # Safety
258    /// 
259    /// 调用者需要确保:
260    /// - 不同线程不会并发写入重叠的内存区域
261    /// - 不会在写入时读取同一区域
262    ///
263    /// 违反这些约束会导致数据竞争,这是未定义行为。
264    ///
265    /// # Parameters
266    /// - `offset`: Write position (byte offset from file start)
267    /// - `data`: Data to write
268    ///
269    /// # Returns
270    /// Number of bytes actually written
271    ///
272    /// # 参数
273    /// - `offset`: 写入位置(从文件开头的字节偏移)
274    /// - `data`: 要写入的数据
275    ///
276    /// # 返回值
277    /// 返回实际写入的字节数
278    ///
279    /// # Examples
280    ///
281    /// ```
282    /// # use ranged_mmap::{MmapFileInner, Result};
283    /// # use tempfile::tempdir;
284    /// # fn main() -> Result<()> {
285    /// # let dir = tempdir()?;
286    /// # let path = dir.path().join("output.bin");
287    /// # use std::num::NonZeroU64;
288    /// let file = MmapFileInner::create(&path, NonZeroU64::new(1024).unwrap())?;
289    ///
290    /// // Concurrent writes to non-overlapping regions using std::thread
291    /// // 使用 std::thread 并发写入不重叠区域
292    /// let file1 = file.clone();
293    /// let file2 = file.clone();
294    ///
295    /// std::thread::scope(|s| {
296    ///     // Safety: Two threads write to non-overlapping regions [0, 5) and [100, 105)
297    ///     // Safety: 两个线程写入不重叠的区域 [0, 5) 和 [100, 105)
298    ///     s.spawn(|| unsafe { file1.write_at(0, b"hello") });
299    ///     s.spawn(|| unsafe { file2.write_at(100, b"world") });
300    /// });
301    ///
302    /// unsafe { file.flush()?; }
303    /// # Ok(())
304    /// # }
305    /// ```
306    ///
307    #[inline]
308    pub unsafe fn write_at(&self, offset: u64, data: &[u8]) -> usize {
309        let offset_usize = offset as usize;
310        let len = data.len();
311
312        debug_assert!(
313            offset_usize.saturating_add(len) <= self.size.get() as usize,
314            "Write would exceed file size: offset={}, len={}, file_size={}",
315            offset, len, self.size.get()
316        );
317
318        // Safety: We assume the caller ensures different threads don't write to overlapping regions
319        // Safety: 我们假设调用者确保不同线程不会写入重叠区域
320        unsafe {
321            let mmap = &mut *self.mmap.get();
322            mmap[offset_usize..offset_usize + len].copy_from_slice(data);
323        }
324
325        len
326    }
327
328    /// Write all data at the specified position
329    ///
330    /// 在指定位置写入所有数据
331    ///
332    /// This method guarantees that all data is written, or returns an error if
333    /// insufficient space is available.
334    ///
335    /// 这个方法保证写入所有数据,如果空间不足会返回错误。
336    ///
337    /// # Safety
338    /// 
339    /// The caller must ensure:
340    /// - Different threads do not write to overlapping memory regions concurrently
341    /// - No reads occur to the same region during writes
342    ///
343    /// # Safety
344    /// 
345    /// 调用者需要确保:
346    /// - 不同线程不会并发写入重叠的内存区域
347    /// - 不会在写入时读取同一区域
348    ///
349    /// # Parameters
350    /// - `offset`: Write position
351    /// - `data`: Data to write
352    ///
353    /// # 参数
354    /// - `offset`: 写入位置
355    /// - `data`: 要写入的数据
356    ///
357    #[inline]
358    pub unsafe fn write_all_at(&self, offset: u64, data: &[u8]) {
359        unsafe { self.write_at(offset, data); }
360    }
361
362    /// Read data at the specified position
363    ///
364    /// 在指定位置读取数据
365    ///
366    /// Reads data from the memory mapping into the buffer.
367    ///
368    /// 从内存映射中读取数据到缓冲区。
369    ///
370    /// # Safety
371    /// 
372    /// The caller must ensure no writes occur to the same region during reads.
373    /// Concurrent reads are safe, but concurrent read-write to the same region
374    /// leads to data races.
375    /// 
376    /// # Safety
377    /// 
378    /// 调用者需要确保不会在读取时写入同一区域。
379    /// 并发读取是安全的,但读写同一区域会导致数据竞争。
380    ///
381    /// # Parameters
382    /// - `offset`: Read position
383    /// - `buf`: Buffer to receive data
384    ///
385    /// # Returns
386    /// Number of bytes actually read
387    ///
388    /// # 参数
389    /// - `offset`: 读取位置
390    /// - `buf`: 接收数据的缓冲区
391    ///
392    /// # 返回值
393    /// 返回实际读取的字节数
394    pub unsafe fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
395        let offset_usize = offset as usize;
396        let len = buf.len();
397
398        if offset_usize >= self.size.get() as usize {
399            return Ok(0);
400        }
401
402        let available = (self.size.get() as usize).saturating_sub(offset_usize).min(len);
403
404        // Safety: Read operation is safe as long as no concurrent writes to the same region
405        // Safety: 读取操作,只要不和写入同一区域并发就是安全的
406        unsafe {
407            let mmap = &*self.mmap.get();
408            buf[..available].copy_from_slice(&mmap[offset_usize..offset_usize + available]);
409        }
410
411        Ok(available)
412    }
413
414    /// Flush data to disk asynchronously
415    ///
416    /// 异步刷新数据到磁盘
417    ///
418    /// Initiates an asynchronous flush operation without blocking for completion.
419    /// The operating system will write data to disk in the background.
420    ///
421    /// 发起异步刷新操作,不会阻塞等待完成。操作系统会在后台将数据写入磁盘。
422    ///
423    /// # Safety
424    /// 
425    /// During the flush, the caller must ensure no other threads are modifying the
426    /// mapped memory. While flush itself is a safe operation, it is marked unsafe
427    /// for API consistency as it operates on data modified through unsafe methods.
428    /// 
429    /// # Safety
430    /// 
431    /// 在刷新期间,调用者需要确保没有其他线程正在修改映射的内存。
432    /// 虽然 flush 本身是安全的操作,但为了保持 API 一致性,
433    /// 它被标记为 unsafe,因为它操作的是通过 unsafe 方法修改的数据。
434    ///
435    /// # Examples
436    ///
437    /// ```
438    /// # use ranged_mmap::{MmapFileInner, Result};
439    /// # use tempfile::tempdir;
440    /// # fn main() -> Result<()> {
441    /// # let dir = tempdir()?;
442    /// # let path = dir.path().join("output.bin");
443    /// # use std::num::NonZeroU64;
444    /// let file = MmapFileInner::create(&path, NonZeroU64::new(1024).unwrap())?;
445    /// unsafe {
446    ///     file.write_all_at(0, b"important data");
447    ///     file.flush()?; // Flush asynchronously to disk
448    ///                    // 异步刷新到磁盘
449    /// }
450    /// # Ok(())
451    /// # }
452    /// ```
453    pub unsafe fn flush(&self) -> Result<()> {
454        unsafe {
455            let mmap = &*self.mmap.get();
456            Ok(mmap.flush_async()?)
457        }
458    }
459
460    /// Flush data to disk synchronously
461    ///
462    /// 同步刷新数据到磁盘
463    ///
464    /// Synchronously flushes data in memory to disk, blocking until completion.
465    /// This is slower than `flush()` but guarantees data has been written to disk.
466    ///
467    /// 同步将内存中的数据刷新到磁盘,阻塞直到完成。
468    /// 这比 `flush()` 慢,但保证数据已经写入磁盘。
469    ///
470    /// # Safety
471    /// 
472    /// During the flush, the caller must ensure no other threads are modifying the
473    /// mapped memory. While sync itself is a safe operation, it is marked unsafe
474    /// for API consistency as it operates on data modified through unsafe methods.
475    /// 
476    /// # Safety
477    /// 
478    /// 在刷新期间,调用者需要确保没有其他线程正在修改映射的内存。
479    /// 虽然 sync 本身是安全的操作,但为了保持 API 一致性,
480    /// 它被标记为 unsafe,因为它操作的是通过 unsafe 方法修改的数据。
481    ///
482    /// # Examples
483    ///
484    /// ```
485    /// # use ranged_mmap::{MmapFileInner, Result};
486    /// # use tempfile::tempdir;
487    /// # fn main() -> Result<()> {
488    /// # let dir = tempdir()?;
489    /// # let path = dir.path().join("output.bin");
490    /// # use std::num::NonZeroU64;
491    /// let file = MmapFileInner::create(&path, NonZeroU64::new(1024).unwrap())?;
492    /// unsafe {
493    ///     file.write_all_at(0, b"critical data");
494    ///     file.sync_all()?; // Ensure data is written to disk
495    ///                       // 确保数据已写入磁盘
496    /// }
497    /// # Ok(())
498    /// # }
499    /// ```
500    pub unsafe fn sync_all(&self) -> Result<()> {
501        unsafe {
502            let mmap = &*self.mmap.get();
503            Ok(mmap.flush()?)
504        }
505    }
506
507    /// Flush a specific range to disk
508    ///
509    /// 刷新指定区域到磁盘
510    ///
511    /// Flushes only a portion of the file to disk, which can improve performance.
512    ///
513    /// 只刷新文件的一部分到磁盘,可以提高性能。
514    ///
515    /// # Safety
516    /// 
517    /// During the flush, the caller must ensure no other threads are modifying
518    /// memory in that region.
519    /// 
520    /// # Safety
521    /// 
522    /// 在刷新期间,调用者需要确保没有其他线程正在修改该区域的内存。
523    ///
524    /// # Parameters
525    /// - `offset`: Start position of the flush range
526    /// - `len`: Length of the flush range
527    ///
528    /// # 参数
529    /// - `offset`: 刷新区域的起始位置
530    /// - `len`: 刷新区域的长度
531    pub unsafe fn flush_range(&self, offset: u64, len: usize) -> Result<()> {
532        let offset_usize = offset as usize;
533
534        debug_assert!(
535            offset_usize.saturating_add(len) <= self.size.get() as usize,
536            "Flush range exceeds file size: offset={}, len={}, file_size={}",
537            offset, len, self.size.get()
538        );
539
540        unsafe {
541            let mmap = &*self.mmap.get();
542            Ok(mmap.flush_async_range(offset_usize, len)?)
543        }
544    }
545
546    /// Get file size
547    /// 
548    /// 获取文件大小
549    #[inline]
550    pub fn size(&self) -> NonZeroU64 {
551        self.size
552    }
553
554    /// Fill the entire file with a specified byte
555    ///
556    /// 填充整个文件为指定字节
557    ///
558    /// Efficiently fills the entire file with the specified value.
559    ///
560    /// 高效地将整个文件填充为指定值。
561    ///
562    /// # Safety
563    /// 
564    /// The caller must ensure no other threads are reading or writing any part
565    /// of the file during the fill. This operation modifies the entire file content.
566    /// 
567    /// # Safety
568    /// 
569    /// 调用者需要确保在填充期间没有其他线程正在读写文件的任何部分。
570    /// 此操作会修改整个文件内容。
571    ///
572    /// # Parameters
573    /// - `byte`: Fill byte value
574    ///
575    /// # 参数
576    /// - `byte`: 填充字节
577    pub unsafe fn fill(&self, byte: u8) -> Result<()> {
578        unsafe {
579            let mmap = &mut *self.mmap.get();
580            mmap.fill(byte);
581        }
582        Ok(())
583    }
584
585    /// Zero out the entire file
586    ///
587    /// 清零整个文件
588    ///
589    /// Fills the entire file with 0.
590    ///
591    /// 将整个文件填充为 0。
592    ///
593    /// # Safety
594    /// 
595    /// The caller must ensure no other threads are reading or writing any part
596    /// of the file during the zeroing. This operation modifies the entire file content.
597    /// 
598    /// # Safety
599    /// 
600    /// 调用者需要确保在清零期间没有其他线程正在读写文件的任何部分。
601    /// 此操作会修改整个文件内容。
602    pub unsafe fn zero(&self) -> Result<()> {
603        unsafe { self.fill(0) }
604    }
605
606    /// Read a specific region into a new Vec
607    ///
608    /// 读取指定区域到新的 Vec
609    ///
610    /// This copies data into a new Vec.
611    ///
612    /// 这会拷贝数据到一个新的 Vec 中。
613    ///
614    /// # Safety
615    /// 
616    /// The caller must ensure no other threads are writing to the region during the read.
617    /// 
618    /// # Safety
619    /// 
620    /// 调用者需要确保在读取期间没有其他线程正在写入该区域。
621    ///
622    /// # Parameters
623    /// - `offset`: Read start position
624    /// - `len`: Read length
625    ///
626    /// # 参数
627    /// - `offset`: 读取起始位置
628    /// - `len`: 读取长度
629    pub unsafe fn read_slice(&self, offset: u64, len: usize) -> Result<Vec<u8>> {
630        let mut buf = vec![0u8; len];
631        let bytes_read = unsafe { self.read_at(offset, &mut buf)? };
632        buf.truncate(bytes_read);
633        Ok(buf)
634    }
635
636    /// Get a raw pointer to the underlying mmap
637    /// 
638    /// 获取底层 mmap 的原始指针
639    /// 
640    /// # Safety
641    /// 
642    /// The caller must ensure:
643    /// - No multiple mutable references are created
644    /// - The pointer lifetime does not exceed MmapFileInner
645    /// 
646    /// # Safety
647    /// 
648    /// 调用者需要确保:
649    /// - 不会创建多个可变引用
650    /// - 指针的生命周期不会超过 MmapFileInner
651    #[inline]
652    pub fn as_ptr(&self) -> *const u8 {
653        unsafe {
654            let mmap = &*self.mmap.get();
655            mmap.as_ptr()
656        }
657    }
658
659    /// Get a mutable raw pointer to the underlying mmap
660    /// 
661    /// 获取底层 mmap 的可变原始指针
662    /// 
663    /// # Safety
664    /// 
665    /// The caller must ensure:
666    /// - No multiple mutable references are created
667    /// - The pointer lifetime does not exceed MmapFileInner
668    /// - No concurrent access to overlapping memory regions
669    /// 
670    /// # Safety
671    /// 
672    /// 调用者需要确保:
673    /// - 不会创建多个可变引用
674    /// - 指针的生命周期不会超过 MmapFileInner
675    /// - 不会并发访问重叠的内存区域
676    #[inline]
677    pub unsafe fn as_mut_ptr(&self) -> *mut u8 {
678        unsafe {
679            let mmap = &mut *self.mmap.get();
680            mmap.as_mut_ptr()
681        }
682    }
683}
684
685/// Implement Debug for MmapFileInner
686/// 
687/// 为 MmapFileInner 实现 Debug
688impl std::fmt::Debug for MmapFileInner {
689    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
690        f.debug_struct("MmapFileInner")
691            .field("size", &self.size)
692            .field("mmap", &"MmapMut")
693            .finish()
694    }
695}
696
697// Implement Send and Sync
698// Safety: Safe as long as users ensure different threads write to non-overlapping regions
699// 
700// 实现 Send 和 Sync
701// Safety: 只要用户确保不同线程写入不重叠区域,就是安全的
702unsafe impl Send for MmapFileInner {}
703unsafe impl Sync for MmapFileInner {}
704