وصف مفصل للبحث عن الأخطاء من مهمة GitLab التي أدت إلى التصحيح لنظام Linux kernel
في 14 سبتمبر ، أبلغ دعم GitLab عن مشكلة خطيرة حدثت لأحد عملائنا: أولاً ، GitLab يعمل بشكل جيد ، ومن ثم يحصل المستخدمون على خطأ. لقد حاولوا استنساخ بعض المستودعات من خلال Git ، وفجأة ظهرت رسالة غير مفهومة حول ملف قديم: Stale file error
. استمر الخطأ لمدة طويلة ولم يعمل حتى بدأ مسؤول النظام يدويًا في الدليل نفسه.
اضطررت لدراسة الآليات الداخلية لجيت ونظام ملفات شبكة NFS. نتيجة لذلك ، وجدنا خللًا في عميل Linux v4.0 NFS ، كتب Trond Myklebust تصحيحًا للنواة ، ومنذ 26 أكتوبر ، تم تضمين هذا التصحيح في نواة Linux الرئيسية .
في هذا المنشور ، سوف أخبرك كيف درسنا المشكلة ، وفي أي اتجاه فكرنا فيه وما هي الأدوات التي استخدمناها لتتبع الخطأ. لقد استلهمنا العمل المباحث الممتاز الذي قام به أوليغ داشفسكي الموضح في المنشور التالي: "كيف أتعقب تسرب الذاكرة في روبي لمدة أسبوعين" .

إنه أيضًا مثال رائع على كيفية تصحيح أخطاء المصادر المفتوحة باعتبارها رياضة جماعية تضم العديد من الأشخاص والشركات والبلدان. شعار GitLab ، " يمكن للجميع المساهمة " ، لا ينطبق فقط على GitLab نفسها ، ولكن أيضًا لمشاريع أخرى مفتوحة المصدر ، مثل نواة Linux.
علة الاستنساخ
احتفظنا بـ NFS على GitLab.com لسنوات عديدة ، ولكن بعد ذلك توقفنا عن استخدامه للوصول إلى بيانات مستودع التخزين على الأجهزة التي تحتوي على تطبيقات. لقد نقلنا جميع مكالمات Git إلى Gitaly . نحن ندعم NFS للعملاء الذين يديرون عمليات التثبيت الخاصة بهم على GitLab لكنهم لم يواجهوا نفس المشكلة التي واجهها العميل المذكور سابقًا.
أعطى العميل بعض التلميحات المفيدة :
- النص الكامل للخطأ:
fatal: Couldn't read ./packed-refs: Stale file handle
. - على ما يبدو ، نشأت المشكلة عندما بدأ العميل يدويًا في جمع البيانات المهملة في Git باستخدام الأمر
git gc
. - اختفى الخطأ عندما بدأ مسؤول النظام الأداة المساعدة
ls
في الدليل. - اختفى الخطأ عند
git gc
عملية git gc
.
من الواضح أن أول نقطتين مرتبطتان. عند إرسال التغييرات إلى فرع Git ، ينشئ Git رابطًا ضعيفًا - اسم ملف طويل يشير إلى اسم الفرع للالتزام. على سبيل المثال ، عند الإرسال إلى master
، سيتم إنشاء ملف يسمى refs/heads/master
في المستودع:
$ cat refs/heads/master 2e33a554576d06d9e71bfd6814ee9ba3a7838963
ينفذ الأمر git gc
العديد من المهام. على سبيل المثال ، تجمع هذه الروابط الضعيفة (المراجع) packed-refs
ملف واحد يسمى packed-refs
. هذا يسرع العمل قليلاً ، لأن قراءة ملف كبير واحد أسهل من العديد من الملفات الصغيرة. على سبيل المثال ، بعد تشغيل الأمر git gc
، قد يبدو ملف packed-refs
كالتالي:
# pack-refs with: peeled fully-peeled sorted 564c3424d6f9175cf5f2d522e10d20d781511bf1 refs/heads/10-8-stable edb037cbc85225261e8ede5455be4aad771ba3bb refs/heads/11-0-stable 94b9323033693af247128c8648023fe5b53e80f9 refs/heads/11-1-stable 2e33a554576d06d9e71bfd6814ee9ba3a7838963 refs/heads/master
كيف يتم إنشاء ملف packed-refs
؟ لمعرفة ذلك ، قمنا بتشغيل الأمر strace git gc
حيث كان لدينا رابط ضعيف. فيما يلي الخطوط ذات الصلة:
28705 open("/tmp/libgit2/.git/packed-refs.lock", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 3 28705 open(".git/packed-refs", O_RDONLY) = 3 28705 open("/tmp/libgit2/.git/packed-refs.new", O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 4 28705 rename("/tmp/libgit2/.git/packed-refs.new", "/tmp/libgit2/.git/packed-refs") = 0 28705 unlink("/tmp/libgit2/.git/packed-refs.lock") = 0
مكالمات النظام أظهرت أن الأمر git gc
:
- افتتح
packed-refs.lock
. هذا يخبر العمليات الأخرى أن ملف packed-refs
مؤمن ولا يمكن تغييره. - افتتح
packed-refs.new
. packed-refs.new
الروابط الضعيفة في packed-refs.new
.- إعادة تسمية
packed-refs.new
إلى packed-refs
. - إزالة
packed-refs.lock
. - إزالة الروابط الضعيفة.
النقطة الأساسية هنا هي النقطة الرابعة ، وهي إعادة التسمية ، حيث يقدم Git ملف packed-refs
. لا يقوم git gc
بجمع الروابط الضعيفة فحسب ، بل يؤدي أيضًا مهمة أكثر كثافة من حيث الموارد - فهو يبحث في الكائنات غير المستخدمة ويزيلها. في المستودعات الكبيرة ، قد يستمر هذا أكثر من ساعة.
وسألنا أنفسنا: في المستودعات الكبيرة هل git gc
تبقي الملف مفتوحًا أثناء التنظيف؟ درسنا سجلات strace
، أطلقنا الأداة المساعدة lsof
، وهنا ما تعلمناه حول عملية git gc
:

كما ترون ، يتم إغلاق ملف packed-refs
في النهاية ، بعد العملية الطويلة المحتملة Garbage collect objects
.
لذا ، نشأ السؤال التالي: كيف يتصرف NFS عندما يكون ملف packed-refs
مفتوحًا على عقدة واحدة ، والآخر يعيد تسميته في ذلك الوقت؟
"لأغراض علمية" ، طلبنا من العميل إجراء تجربة واحدة على جهازين مختلفين (أليس وبوب):
1) في وحدة التخزين المشتركة NFS ، قم بإنشاء ملفين: test1.txt
و test2.txt
بمحتويات مختلفة ، بحيث يكون التمييز بينهما أسهل:
alice $ echo "1 - Old file" > /path/to/nfs/test1.txt alice $ echo "2 - New file" > /path/to/nfs/test2.txt
2) على جهاز Alice ، يجب أن يكون الملف test1.txt
مفتوحًا:
alice $ irb irb(main):001:0> File.open('/path/to/nfs/test1.txt')
3) على جهاز Alice ، اعرض باستمرار محتويات test1.txt
:
alice $ while true; do cat test1.txt; done
4) ثم ، على جهاز بوب ، قم بتشغيل الأمر:
bob $ mv -f test2.txt test1.txt
الخطوة الأخيرة تستنسخ ما يفعل git gc
مع ملف packed-refs
عند الكتابة فوق ملف موجود.
على جهاز العميل ، كانت النتيجة تبدو مثل هذا:
1 - Old file 1 - Old file 1 - Old file cat: test1.txt: Stale file handle
هناك! يبدو أننا قد سيطرنا على المشكلة بطريقة مسيطر عليها. ولكن في نفس التجربة على خادم Linux NFS ، لم تحدث هذه المشكلة. كانت النتيجة متوقعة - بعد إعادة تسمية المحتوى الجديد تم قبوله:
1 - Old file 1 - Old file 1 - Old file 2 - New file <--- RENAME HAPPENED 2 - New file 2 - New file
من أين يأتي هذا الاختلاف في السلوك؟ اتضح أن العميل يستخدم تخزين Isilon NFS ، والذي يدعم NFS v4.0 فقط. عندما قمنا بتغيير إعدادات الاتصال إلى الإصدار vers=4.0
باستخدام المعلمة vers=4.0
في /etc/fstab
، أظهر الاختبار نتيجة مختلفة لخادم Linux NFS:
1 - Old file 1 - Old file 1 - Old file 1 - Old file <--- RENAME HAPPENED 1 - Old file 1 - Old file
بدلاً من Stale file handle
القديم Stale file handle
يعرض خادم Linux NFS v4.0 محتوى قديمًا. اتضح أن الاختلاف في السلوك يمكن تفسيره بواسطة مواصفات NFS. من RFC 3010 :
قد يصبح واصف الملف قديمًا أو منتهي الصلاحية عند إعادة تسميته ، ولكن ليس دائمًا. يُنصح منفذي الخادم باتخاذ خطوات للتأكد من أن واصفات الملف لا تنتهي صلاحيتها ولا تنتهي صلاحيتها بهذه الطريقة.
بمعنى آخر ، يمكن لخوادم NFS اختيار كيفية التصرف عند إعادة تسمية أحد الملفات ، ويقوم خادم NFS بإرجاع Stale file error
بشكل معقول في مثل هذه الحالات. اقترحنا أن سبب المشكلة هو نفسه ، على الرغم من أن النتائج كانت مختلفة. لقد شكنا في أنه تم فحص ذاكرة التخزين المؤقت ، لأن الأداة المساعدة ls
في الدليل أزالت الخطأ. الآن لدينا سيناريو اختبار قابل للتكرار ، وانتقلنا إلى الخبراء - مشرفو Linux NFS.
تتبع خطأ: تفويض على خادم NFS
عندما تمكنا من إعادة إنتاج الخطأ خطوة بخطوة ، كتبت إلى جهات اتصال Linux NFS حول ما تعلمناه. لقد تواصلت مع Bruce Fields ، مشرف خادم Linux NFS لمدة أسبوع ، واقترح أن يكون الخطأ في NFS وأنني بحاجة لدراسة حركة مرور الشبكة. كان يعتقد أن المشكلة تتمثل في تفويض المهام على خادم NFS.
ما هو التفويض على خادم NFS؟
باختصار ، يحتوي الإصدار NFS v4 على وظيفة تفويض لتسريع الوصول إلى الملفات. يمكن أن يفوض الخادم حق الوصول للقراءة أو الكتابة إلى العميل بحيث لا يضطر العميل إلى سؤال الخادم باستمرار إذا كان الملف قد تم تغييره بواسطة عميل آخر. ببساطة ، تفويض سجل مثل إقراض شخص ما دفتر ملاحظاتك والقول ، "أنت تكتب هنا ، وسأحصل عليه عندما أكون جاهزًا". ولا يتعين على أي شخص أن يطلب دفتر ملاحظات في كل مرة تحتاج فيها إلى كتابة شيء ما - يتمتع بحرية كاملة في التصرف حتى يتم إخراج دفتر الملاحظات. في NFS ، يسمى طلب إرجاع دفتر ملاحظات بإلغاء التفويض.
خطأ في إبطال تفويض NFS يمكن أن يفسر مشكلة Stale file handle
. تذكر كيف تم فتح test1.txt
في test1.txt
Alice ، ثم قام test2.txt
باستبداله. ربما تعذر على الخادم إبطال التفويض لـ test1.txt
، وأدى ذلك إلى حالة غير صالحة. لاختبار هذه النظرية ، قمنا بتسجيل حركة مرور NFC باستخدام الأداة المساعدة tcpdump
وتصورها باستخدام Wireshark.
Wireshark هو أداة رائعة مفتوحة المصدر لتحليل حركة مرور الشبكة ، خاصة لاستكشاف NFS أثناء العمل. سجلنا التتبع باستخدام الأمر التالي على خادم NFS:
tcpdump -s 0 -w /tmp/nfs.pcap port 2049
يسجل هذا الأمر جميع زيارات NFS التي عادة ما تمر عبر منفذ TCP 2049. نظرًا لأن تجربتنا كانت ناجحة مع NFS v4.1 ، ولكن ليس مع NFS v4.0 ، يمكننا مقارنة سلوك NFS في حالة العمل وغير العاملة. مع Wireshark ، شاهدنا السلوك التالي:
NFS v4.0 (ملف مهم)

يوضح هذا المخطط أنه في الخطوة 1 ، يفتح Alice test1.txt
ويتلقى واصف ملف NFS مع معرف stateid
0x3000. عندما يحاول بوب إعادة تسمية الملف ، يطلب خادم NFS إعادة المحاولة عن طريق إرسال الرسالة NFS4ERR_DELAY
، ويتذكر الوفد من Alice عبر الرسالة CB_RECALL
(الخطوة 3). إرجاع Alice التفويض (DELEGRETURN في الخطوة 4) ، ويحاول Bob إرسال رسالة إعادة RENAME
مرة أخرى (الخطوة 5). يتم تنفيذ RENAME
في كلتا الحالتين ، لكن Alice تواصل قراءة الملف بنفس الواصف.
NFS v4.1 (حالة العمل)

هنا يكون الفرق مرئيًا في الخطوة 6. في NFS v4.0 (مع ملف قديم) ، تحاول Alice استخدام نفس stateid
. في الإصدار NFS v4.1 (حالة العمل) ، تقوم Alice بتنفيذ عمليات LOOKUP
و OPEN
إضافية ، وبالتالي يعرض الخادم حالة مختلفة. في الإصدار 4.0 ، لا يرسل أي رسائل إضافية. هذا ما يفسر لماذا ترى أليس محتوى قديمًا - تستخدم واصفًا قديمًا.
لماذا تقرر أليس فجأة على LOOKUP
إضافية؟ يبدو أن استدعاء الوفد كان ناجحًا ، لكن يبدو أن هناك مشكلة ما زالت قائمة. على سبيل المثال ، يتم تخطي خطوة الإعاقة. للتحقق من ذلك ، استبعدنا تفويض NFS على خادم NFS نفسه باستخدام هذا الأمر:
echo 0 > /proc/sys/fs/leases-enable
كررنا التجربة ، لكن المشكلة لم تختف. لقد تأكدنا من أن المشكلة لم تكن في خادم NFS أو التفويض ، وقررنا أن ننظر إلى عميل NFS في النواة.
حفر أعمق: عميل Linux NFS
كان السؤال الأول الذي كان علينا الإجابة عليه على مشرفي NFS:
هل استمرت هذه المشكلة في أحدث إصدار من kernel؟
حدثت المشكلة في نواة CentOS 7.2 و Ubuntu 16.04 مع الإصدارات 3.10.0-862.11.6 و 4.4.0-130 ، على التوالي. لكن كلا النوى تخلف عن أحدث إصدار ، والذي كان في ذلك الوقت 4.19-RC2.
لقد قمنا بنشر الجهاز الظاهري الجديد لـ Ubuntu 16.04 على Google Cloud Platform (GCP) ، واستنساخ أحدث نواة Linux ، وإعداد بيئة تطوير kernel. أنشأنا ملف .config
باستخدام menuconfig
مما يلي:
- يتم تصنيف برنامج التشغيل NFS كوحدة نمطية (
CONFIG_NFSD=m
). - تم تحديد معلمات kernel GCP الصحيحة بشكل صحيح.
تتبع علم الوراثة التطور في الوقت الحقيقي بواسطة Drosophila ، ومع العنصر الأول ، يمكننا بسرعة إجراء تصحيحات على عميل NFS دون إعادة تشغيل النواة. النقطة الثانية تضمن أن النواة ستبدأ بعد التثبيت. لحسن الحظ ، كنا راضين عن معلمات kernel الافتراضية.
لقد تأكدنا من أن مشكلة الملف المتقادم لم تختف في أحدث إصدار من kernel. سألنا أنفسنا:
- أين بالضبط تنشأ المشكلة؟
- لماذا يحدث هذا في NFS v4.0 ، ولكن ليس في v4.1؟
للإجابة على هذه الأسئلة ، بحثنا في شفرة مصدر NFS. لم يكن لدينا مصحح أخطاء kernel ، لذلك أرسلنا نوعين من المكالمات إلى الكود المصدري:
pr_info()
(كانت printk
).dump_stack()
: يُظهر تتبع المكدس لاستدعاء الوظيفة الحالية.
على سبيل المثال ، أول شيء فعلناه هو الاتصال nfs4_file_open()
في fs/nfs/nfs4file.c
:
static int nfs4_file_open(struct inode *inode, struct file *filp) { ... pr_info("nfs4_file_open start\n"); dump_stack();
بالطبع ، يمكننا dprintk
مع تصحيح أخطاء Linux أو استخدام rpcdebug
، لكننا أردنا إضافة رسالتنا الخاصة للتحقق من التغييرات.
بعد كل تغيير ، قمنا بإعادة ترجمة الوحدة النمطية وإعادة تثبيتها في النواة باستخدام الأوامر:
make modules sudo umount /mnt/nfs-test sudo rmmod nfsv4 sudo rmmod nfs sudo insmod fs/nfs/nfs.ko sudo mount -a
باستخدام وحدة NFS ، تمكنا من تكرار التجارب وتلقي الرسائل لفهم كود NFS. على سبيل المثال ، يمكنك أن ترى على الفور ما يحدث عندما open()
التطبيق open()
مكالمات:
Sep 24 20:20:38 test-kernel kernel: [ 1145.233460] Call Trace: Sep 24 20:20:38 test-kernel kernel: [ 1145.233462] dump_stack+0x8e/0xd5 Sep 24 20:20:38 test-kernel kernel: [ 1145.233480] nfs4_file_open+0x56/0x2a0 [nfsv4] Sep 24 20:20:38 test-kernel kernel: [ 1145.233488] ? nfs42_clone_file_range+0x1c0/0x1c0 [nfsv4] Sep 24 20:20:38 test-kernel kernel: [ 1145.233490] do_dentry_open+0x1f6/0x360 Sep 24 20:20:38 test-kernel kernel: [ 1145.233492] vfs_open+0x2f/0x40 Sep 24 20:20:38 test-kernel kernel: [ 1145.233493] path_openat+0x2e8/0x1690 Sep 24 20:20:38 test-kernel kernel: [ 1145.233496] ? mem_cgroup_try_charge+0x8b/0x190 Sep 24 20:20:38 test-kernel kernel: [ 1145.233497] do_filp_open+0x9b/0x110 Sep 24 20:20:38 test-kernel kernel: [ 1145.233499] ? __check_object_size+0xb8/0x1b0 Sep 24 20:20:38 test-kernel kernel: [ 1145.233501] ? __alloc_fd+0x46/0x170 Sep 24 20:20:38 test-kernel kernel: [ 1145.233503] do_sys_open+0x1ba/0x250 Sep 24 20:20:38 test-kernel kernel: [ 1145.233505] ? do_sys_open+0x1ba/0x250 Sep 24 20:20:38 test-kernel kernel: [ 1145.233507] __x64_sys_openat+0x20/0x30 Sep 24 20:20:38 test-kernel kernel: [ 1145.233508] do_syscall_64+0x65/0x130
ما هي هذه vfs_open
و vfs_open
؟ لدى Linux نظام ملفات افتراضي ( VFS ) ، طبقة تجريدية توفر واجهة مشتركة لجميع أنظمة الملفات. وثائق VFS تقول:
تطبق VFS open (2) ، stat (2) ، chmod (2) ومكالمات النظام الأخرى. يستخدم نظام VFS وسيطة اسم المسار التي يتم تمريرها إليهم للبحث في ذاكرة التخزين المؤقت عن إدخالات الدليل (ذاكرة التخزين المؤقت لطب الأسنان أو ذاكرة التخزين المؤقت). يوفر هذا محرك بحث سريعًا جدًا يحول اسم المسار (أو اسم الملف) إلى طبقة أسنان معينة. توجد Dentry في ذاكرة الوصول العشوائي ولا يتم حفظها على القرص - فهي موجودة فقط من أجل الأداء.
وفجر علينا - ماذا لو كانت المشكلة في ذاكرة التخزين المؤقت الأسنان؟
لقد لاحظنا أن ذاكرة التخزين المؤقت لطب الأسنان يتم فحصها عادة في fs/nfs/dir.c
كنا مهتمين بشكل خاص nfs4_lookup_revalidate()
، nfs4_lookup_revalidate()
، nfs4_lookup_revalidate()
تعمل في وقت مبكر:
diff --git a/fs/nfs/dir.cb/fs/nfs/dir.c index 8bfaa658b2c1..ad479bfeb669 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1159,6 +1159,7 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags) trace_nfs_lookup_revalidate_enter(dir, dentry, flags); error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, fhandle, fattr, label); trace_nfs_lookup_revalidate_exit(dir, dentry, flags, error); + goto out_bad; if (error == -ESTALE || error == -ENOENT) goto out_bad; if (error)
وفي هذه التجربة ، لم تحدث مشكلة ملف عفا عليها الزمن! وأخيرا ، هاجمنا درب.
لمعرفة سبب عدم حدوث المشكلة في pr_info()
NFS v4.1 ، أضفنا المكالمات pr_info()
إلى كل if
حظر في هذه الوظيفة. قمنا بتجربة NFS v4.0 و v4.1 ووجدنا حالة خاصة في الإصدار v4.1:
if (NFS_SB(dentry->d_sb)->caps & NFS_CAP_ATOMIC_OPEN_V1) { goto no_open; }
ما هو NFS_CAP_ATOMIC_OPEN_V1
؟ يقول تصحيح kernel أن هذه إحدى ميزات NFS v4.1 ، وأكدت الكود في fs/nfs/nfs4proc.c
أن هذه المعلمة هي في v4.1 لكن ليست في v4.0:
static const struct nfs4_minor_version_ops nfs_v4_1_minor_ops = { .minor_version = 1, .init_caps = NFS_CAP_READDIRPLUS | NFS_CAP_ATOMIC_OPEN | NFS_CAP_POSIX_LOCK | NFS_CAP_STATEID_NFSV41 | NFS_CAP_ATOMIC_OPEN_V1
لذلك ، تتصرف الإصدارات بشكل مختلف - في الإصدار v4.1 ، يستدعي goto no_open
المزيد من عمليات التحقق في وظيفة nfs_lookup_revalidate()
، وفي v4.0 ترجع الدالة nfs4_lookup_revalidate()
. وكيف حلنا المشكلة؟
الحل
تحدثت عن النتائج التي توصلنا إليها في القائمة البريدية NFS واقترح التصحيح البدائي . بعد أسبوع ، أرسل Trond Myklebust سلسلة من التصحيحات مع إصلاحات الأخطاء إلى القائمة البريدية ووجد مشكلة أخرى ذات صلة في NFS v4.1 .
اتضح أن الإصلاح ل NFS v4.0 علة كان أعمق في قاعدة التعليمات البرمجية مما كنا نظن. وصفه تروند جيدا في التصحيح :
من الضروري التأكد من التحقق بشكل صحيح من inode و dentry عند فتح ملف مفتوح بالفعل. في الوقت الحالي ، لا نقوم بالتحقق من إما NFSv4.0 ، لأن الملف المفتوح يتم تخزينه مؤقتًا. دعونا إصلاح هذا وتخزين الملفات المفتوحة فقط في حالات خاصة - لاستعادة الملفات المفتوحة وإعادة التفويض.
لقد تأكدنا من أن هذا الإصلاح حل مشكلة الملف المتقادم وأرسل تقارير الأخطاء إلى فرق Ubuntu و RedHat .
لقد فهمنا جيدًا أن التغييرات لن تكون في إصدار kernel المستقر بعد ، لذلك أضفنا حلاً مؤقتًا لهذه المشكلة في Gitaly . لقد جربنا وتحققنا من أن استدعاء stat()
في ملف packed-refs
يتسبب في قيام kernel بالتحقق من الملف المعاد تسميته في ذاكرة التخزين المؤقت لطب الأسنان. للبساطة ، قمنا بتطبيق هذا في Gitaly لأي نظام ملفات ، وليس فقط NFS. يتم التحقق من الصحة مرة واحدة فقط قبل أن يفتح Gitaly المستودع ، وبالنسبة للملفات الأخرى ، توجد بالفعل مكالمات stat()
.
ماذا تعلمنا
يمكن أن يخفي الخطأ في أي ركن من أركان مجموعة البرامج ، وفي بعض الأحيان تحتاج إلى البحث عنه خارج التطبيق. إذا كان لديك اتصالات مفيدة في عالم المصادر المفتوحة ، فإن هذا سيجعل عملك أسهل.
شكرًا جزيلاً لـ Trond Myuklebust على إصلاح المشكلة ، وإلى Bruce Fields للإجابة على أسئلتنا والمساعدة في معرفة NFS. من أجل هذه الاستجابة والكفاءة المهنية ، نقدر مجتمع مطوري البرامج مفتوحة المصدر.