DeepSeek Harness: التنظيف التلقائي و ctx.effect()

آخر تحديث: 2026-08-31

أقوى تصاميم Cordis هو التنظيف التلقائي — عند إلغاء تحميل إضافة، تُستعاد جميع الموارد المُسجّلة عبر ctx تلقائياً دون حاجة لتحرير يدوي. لكن عندما لا تُدار الموارد مباشرة بواسطة ctx، ctx.effect() هو نقطة دخولك للتنظيف اليدوي.

💡 نصيحة: التنظيف التلقائي هو شبكة أمان Cordis؛ ctx.effect() هو مُكمّلك. المبدأ: استخدم تسجيل ctx متى أمكن؛ استخدم ctx.effect() فقط عندما لا تستطيع.

📋 المتطلبات المسبقة: أكمل 11-first-plugin.md، تفهم دالة apply و Context

1. ما ستتعلمه

تنظيف التأثير


2. مبدأ التنظيف التلقائي

(1) ctx هو مركز تسجيل الموارد

كل نسخة ctx تحتفظ بسجل يُسجّل جميع الموارد القابلة للتنظيف التي سجّلتها الإضافة:

TYPESCRIPT
class Context {
  private _disposables: Disposable[] = []
  
  register(disposable: Disposable) {
    this._disposables.push(disposable)
  }
  
  dispose() {
    for (const d of this._disposables.reverse()) {
      d.dispose()
    }
  }
}

عند إلغاء تحميل الإضافة، ctx.dispose() يستعيد جميع الموارد بترتيب التسجيل العكسي.

(2) أنواع الموارد القابلة للتنظيف التلقائي

الموارد التالية المُسجّلة عبر ctx تُنظّف تلقائياً جميعها:

طريقة التسجيل سلوك التنظيف
ctx.on('event', handler) إزالة مستمع الحدث
ctx.setInterval(fn, ms) مسح المؤقت
ctx.setTimeout(fn, ms) مسح المؤقت
ctx.command('name') إلغاء تسجيل الأمر
ctx.service('name', impl) إلغاء تسجيل الخدمة

(3) ▶ مثال 3

TYPESCRIPT
import { Context } from '@deepseek-ai/cordis'

export const name = 'auto-cleanup-demo'

export function apply(ctx: Context) {
  // جميع التسجيلات أدناه تُنظّف تلقائياً
  ctx.on('session/created', (s) => {
    ctx.logger.info(`session: ${s.id}`)
  })

  ctx.setInterval(() => {
    ctx.logger.info('tick')
  }, 10000)

  ctx.command('demo')
    .action(() => 'demo command')
}
// عند إلغاء تحميل الإضافة: المستمع أُزيل + المؤقت مُسح + الأمر أُلغي، صفر كود يدوي

3. ctx.effect(): تنظيف الموارد يدوياً

(1) لماذا الحاجة للتنظيف اليدوي

ليست كل الموارد يمكن تسجيلها مباشرة عبر ctx. مثلاً:

هنا يأتي دور ctx.effect():

TYPESCRIPT
ctx.effect(() => {
  // أعد دالة تنظيف
  return () => {
    // منطق التنظيف
  }
})

(2) ▶ مثال 2

TYPESCRIPT
import { Context } from '@deepseek-ai/cordis'

export const name = 'manual-cleanup'

export function apply(ctx: Context) {
  const connection = createExternalConnection()

  ctx.effect(() => {
    return () => {
      connection.close()
      ctx.logger.info('connection closed')
    }
  })
}

ctx.effect() يستقبل دالة مصنع تُعيد دالة تنظيف. عند إلغاء تحميل الإضافة، Cordis يستدعي دالة التنظيف لتحرير الموارد.

(3) ▶ مثال 3

TYPESCRIPT
// النمط 1: إعادة دالة التنظيف (مُوصى به)
ctx.effect(() => {
  const ws = new WebSocket('ws://localhost:8080')
  return () => ws.close()
})

// النمط 2: تمرير مرجع دالة التنظيف
const cleanup = () => { /* ... */ }
ctx.effect(cleanup)

ميزة النمط 1 أن إنشاء المورد وتنظيفه في نفس الإغلاق، مما يحافظ على تماسك المنطق.


4. نمط دالة التنظيف المُعادة

(1) النمط القياسي

TYPESCRIPT
ctx.effect(() => {
  const resource = acquireResource()
  
  return () => {
    releaseResource(resource)
  }
})

هذا النمط "اكتساب-تحرير" مشابه لـ try-finally:

TYPESCRIPT
// نموذج try-finally المُكافئ ذهنياً
try {
  const resource = acquireResource()
  // استخدام المورد
} finally {
  releaseResource(resource)
}

(2) تنظيف موارد متعددة

TYPESCRIPT
export function apply(ctx: Context) {
  ctx.effect(() => {
    const db = openDatabase()
    const cache = openCache()
    
    return () => {
      cache.close()  // أغلق التابع أولاً
      db.close()     // ثم أغلق ما يعتمد عليه
    }
  })
}

⚠️ ترتيب التنظيف مهم — أغلق الكائنات التي تعتمد على موارد أخرى أولاً، ثم أغلق الموارد التي تعتمد عليها.

(3) معالجة الأخطاء في دوال التنظيف

TYPESCRIPT
ctx.effect(() => {
  const conn = createConnection()
  return () => {
    try {
      conn.close()
    } catch (e) {
      ctx.logger.warn('cleanup error:', e)
    }
  }
})

الاستثناءات في دوال التنظيف لا يجب أن تقطع تنظيف الموارد الأخرى. Cordis يحمي كل دالة تنظيف بـ try-catch داخلياً، لكن المعالجة الصريحة أكثر أماناً.


5. التنظيف الصحيح لـ setInterval/setTimeout

(1) طريقة التنظيف التلقائي (مُوصى بها)

TYPESCRIPT
export function apply(ctx: Context) {
  // استخدم ctx.setInterval — تنظيف تلقائي
  ctx.setInterval(() => {
    ctx.logger.info('heartbeat')
  }, 30000)
}

(2) واجهة API الأصلية + ctx.effect()

إذا كان يجب استخدام setInterval الأصلية:

TYPESCRIPT
export function apply(ctx: Context) {
  const timer = setInterval(() => {
    ctx.logger.info('heartbeat')
  }, 30000)

  ctx.effect(() => {
    return () => clearInterval(timer)
  })
}

(3) المقارنة

الطريقة حجم الكود الموثوقية مُوصى بها
ctx.setInterval سطر 1 عالية (تلقائية)
أصلية + ctx.effect() 3 أسطر متوسطة (يدوية) ⚠️
أصلية (بدون تنظيف) سطر 1 منخفضة (تسرب)

(4) فخ setTimeout

TYPESCRIPT
// ❌ خطأ: setTimeout يظل يعمل بعد إلغاء التحميل
export function apply(ctx: Context) {
  setTimeout(() => {
    ctx.logger.info('delayed action') // الإضافة قد تكون أُلغي تحميلها بالفعل!
  }, 5000)
}
TYPESCRIPT
// ✅ صحيح: استخدم ctx.setTimeout
export function apply(ctx: Context) {
  ctx.setTimeout(() => {
    ctx.logger.info('delayed action') // لن يعمل بعد إلغاء التحميل
  }, 5000)
}

6. أنماط تنظيف اتصالات الشبكة

(1) خادم HTTP

TYPESCRIPT
import { createServer } from 'http'

export function apply(ctx: Context) {
  const server = createServer((req, res) => {
    res.end('ok')
  })

  server.listen(3456)

  ctx.effect(() => {
    return () => {
      server.close()
      ctx.logger.info('HTTP server closed')
    }
  })
}

(2) اتصال WebSocket

TYPESCRIPT
import WebSocket from 'ws'

export function apply(ctx: Context) {
  const ws = new WebSocket('ws://localhost:8080')

  ws.on('open', () => {
    ctx.logger.info('ws connected')
  })

  ctx.effect(() => {
    return () => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.close()
      }
    }
  })
}

(3) تجمع اتصال قاعدة البيانات

TYPESCRIPT
import { Pool } from 'pg'

export function apply(ctx: Context) {
  const pool = new Pool({
    connectionString: 'postgresql://localhost/mydb',
    max: 10
  })

  ctx.effect(() => {
    return async () => {
      await pool.end()
      ctx.logger.info('db pool closed')
    }
  })
}

⚠️ دوال التنظيف يمكن أن تكون غير متزامنة. Cordis سينتظر اكتمال التنظيف غير المتزامن قبل المتابعة بالتنظيف اللاحق.

(4) تنظيف مستمعي الأحداث

TYPESCRIPT
export function apply(ctx: Context) {
  const emitter = getExternalEmitter()

  const handler = (data: any) => {
    ctx.logger.info('event:', data)
  }

  emitter.on('data', handler)

  ctx.effect(() => {
    return () => {
      emitter.off('data', handler)
    }
  })
}

7. أخطاء التنظيف الشائعة

(1) نسيان تسجيل التنظيف

TYPESCRIPT
// ❌ تسرب: المؤقت يظل يعمل بعد إلغاء التحميل
export function apply(ctx: Context) {
  setInterval(() => {
    console.log('orphan timer')
  }, 1000)
}

الحل: استخدم ctx.setInterval أو سجّل ctx.effect().

(2) ترتيب تنظيف خاطئ

TYPESCRIPT
// ❌ إغلاق قاعدة البيانات أولاً، ثم إغلاق الذاكرة المؤقتة التي تعتمد عليها
ctx.effect(() => {
  const db = openDB()
  const cache = new Cache(db)
  return () => {
    db.close()      // أُغلقت db أولاً
    cache.close()   // الذاكرة المؤقتة تصل لـ db داخلياً → خطأ
  }
})

الحل: اعكس ترتيب التنظيف.

(3) استثناء غير ملتقط في التنظيف

TYPESCRIPT
// ❌ دالة التنظيف ترمي استثناءً، تقطع التنظيف اللاحق
ctx.effect(() => {
  return () => {
    throw new Error('cleanup failed')  // تأثيرات أخرى قد لا تُنفّذ
  }
})

الحل: غلّف منطق التنظيف بـ try-catch.

(4) مرجع إغلاق قديم

TYPESCRIPT
// ❌ يشير لمتغير خارجي قد يكون غير صالح بعد إلغاء التحميل
let globalRef: SomeObject | null = new SomeObject()

export function apply(ctx: Context) {
  ctx.effect(() => {
    return () => {
      globalRef!.cleanup()  // globalRef قد أُعيّن لـ null بكود آخر
    }
  })
}

الحل: التقط المرجع داخل إغلاق effect.


❓ أسئلة شائعة

س هل يمكن أن تكون دوال تنظيف ctx.effect() غير متزامنة؟
ج نعم. Cordis يدعم دوال التنظيف غير المتزامنة وينتظر اكتمالها قبل المتابعة. ملاحظة: إذا أخذ التنظيف وقتاً طويلاً، سيؤخر إلغاء تحميل الإضافة بالكامل.
س ما ترتيب تنفيذ استدعاءات ctx.effect() المتعددة؟
ج ترتيب التسجيل العكسي (LIFO، كالمكدس). التأثيرات المُسجّلة لاحقاً تُنظّف أولاً، مما يضمن ترتيب الاعتماديات الصحيح.
س هل تُنظّف الموارد إذا تعطّلت الإضافة؟
ج نعم. Cordis لا يزال يحاول استدعاء جميع دوال التنظيف المُسجّلة عند خروج الإضافة بشكل غير طبيعي، بما فيها المُسجّلة عبر ctx.effect(). هذا ضمان أمان Cordis.
س هل يمكنني تسجيل ctx.effect() خارج apply؟
ج تقنياً نعم (طالما تحتفظ بمرجع ctx)، لكن غير مُوصى به. التأثيرات المُسجّلة خارج apply لا تنتمي لأي دورة حياة إضافة وقد تُسبب سلوك تنظيف غير متوقع.
س ما الفرق بين ctx.on() و ctx.effect()؟
ج ctx.on() يُسجّل مستمع حدث يُزال تلقائياً عند إلغاء التحميل. ctx.effect() يُسجّل دالة تنظيف عادية تُستدعى عند إلغاء التحميل. كلاهما يُكمّل الآخر: ctx.on يعالج الأحداث، ctx.effect يعالج الموارد الأخرى.

📖 ملخص


📝 تمارين

1. ⭐ أساسي: اكتب إضافة تُخرج عدّاداً كل ثانية باستخدام ctx.setInterval. ابدأها وتأكد أن المؤقت يُنظّف بشكل صحيح عند إلغاء التحميل.

2. ⭐⭐ متوسط: اكتب إضافة تنشئ خادم HTTP يستمع على المنفذ 3456، مع تسجيل التنظيف عبر ctx.effect(). ابدأها، اختبر طلبات HTTP، ثم ألغِ تحميل الإضافة وتأكد أن المنفذ حُرّر.

3. ⭐⭐⭐ تحدٍ: اكتب إضافة تدير اتصال WebSocket وتجمع اتصال قاعدة بيانات، مع التأكد أن التنظيف يُغلق WebSocket أولاً ثم قاعدة البيانات، مع معالجة الاستثناءات في دوال التنظيف. الاختبار: ارمِ خطأً متعمداً أثناء إغلاق قاعدة البيانات، وتحقق أن WebSocket أُغلق بشكل صحيح رغم ذلك.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%