কেন হাস্কেলের বন্ধনী ফাংশন এক্সিকিউটেবলে কাজ করে কিন্তু পরীক্ষায় পরিষ্কার করতে ব্যর্থ হয়?


10

আমি খুব অদ্ভুত আচরণ দেখছি যেখানে হাস্কেলের bracketফাংশন ব্যবহৃত হয়েছে stack runবা stack testব্যবহৃত হয়েছে তার উপর নির্ভর করে আলাদা আচরণ করছে ving

নিম্নলিখিত কোডটি বিবেচনা করুন, যেখানে ডকার পাত্রে তৈরি করতে এবং পরিষ্কার করতে দুটি নেস্টেড বন্ধনী ব্যবহার করা হয়:

module Main where

import Control.Concurrent
import Control.Exception
import System.Process

main :: IO ()
main = do
  bracket (callProcess "docker" ["run", "-d", "--name", "container1", "registry:2"])
          (\() -> do
              putStrLn "Outer release"
              callProcess "docker" ["rm", "-f", "container1"]
              putStrLn "Done with outer release"
          )
          (\() -> do
             bracket (callProcess "docker" ["run", "-d", "--name", "container2", "registry:2"])
                     (\() -> do
                         putStrLn "Inner release"
                         callProcess "docker" ["rm", "-f", "container2"]
                         putStrLn "Done with inner release"
                     )
                     (\() -> do
                         putStrLn "Inside both brackets, sleeping!"
                         threadDelay 300000000
                     )
          )

যখন আমি এটি দিয়ে চালিত করি stack runএবং এতে বাধা Ctrl+Cপাই, তখন আমি প্রত্যাশিত আউটপুটটি পাই:

Inside both brackets, sleeping!
^CInner release
container2
Done with inner release
Outer release
container1
Done with outer release

এবং আমি যাচাই করতে পারি যে উভয় ডকার পাত্রে তৈরি হয়েছে এবং তারপরে সরানো হয়েছে।

যাইহোক, আমি যদি এই একই কোডটি পরীক্ষায় আটকান এবং চালিত হয় তবে stack testকেবলমাত্র (অংশ) প্রথম ক্লিনআপ ঘটে:

Inside both brackets, sleeping!
^CInner release
container2

এর ফলস্বরূপ একটি ডকারের ধারকটি আমার মেশিনে চালিত রেখে গেছে। কি হচ্ছে?

  • আমি নিশ্চিত করেছি যে ghc-optionsউভয়কে যথাযথটি পাস করা হয়েছে।
  • এখানে পূর্ণ বিক্ষোভের রেপো: https://github.com/thomasjm/bracket-issue

স্ট্যাক পরীক্ষা কি থ্রেড ব্যবহার করে?
কার্ল

1
আমি নিশ্চিত নই. আমি একটি আকর্ষণীয় তথ্য লক্ষ্য করেছি: যদি আমি প্রকৃত সংকলিত পরীক্ষাটি নির্বাহযোগ্য .stack-workএবং এটি সরাসরি চালিত করি তবে সমস্যাটি ঘটে না। এটি কেবল তখনই ঘটে যখন এর অধীনে চলমান stack test
টম

আমি অনুমান করতে পারি কী চলছে, তবে আমি স্ট্যাকটি মোটেই ব্যবহার করি না। এটি আচরণের ভিত্তিতে একটি অনুমান মাত্র। 1) stack testপরীক্ষা পরিচালনা করার জন্য কর্মী থ্রেড শুরু করে। 2) সিগিন্ট হ্যান্ডলারটি মূল থ্রেডটিকে হত্যা করে। 3) কোনও অতিরিক্ত থ্রেড উপেক্ষা করে মূল থ্রেড করলে হাস্কেল প্রোগ্রামগুলি সমাপ্ত হয়। 2 হ'ল জিএইচসি দ্বারা সংকলিত প্রোগ্রামগুলির জন্য SIGINT এ ডিফল্ট আচরণ। 3 হ্যাডকেলে থ্রেডগুলি কীভাবে কাজ করে। 1 সম্পূর্ণ অনুমান।
কার্ল

উত্তর:


6

আপনি যখন ব্যবহার করেন stack runস্ট্যাক কার্যকরভাবে execএক্সিকিউটেবলের কাছে নিয়ন্ত্রণ স্থানান্তর করতে একটি সিস্টেম কল ব্যবহার করে, সুতরাং নতুন এক্সিকিউটেবলের জন্য প্রক্রিয়াটি চলমান স্ট্যাক প্রক্রিয়াটির পরিবর্তে, আপনি শেল থেকে সরাসরি এক্সিকিউটেবল চালাবেন বলে মনে করেন। প্রক্রিয়া গাছের পরে দেখতে কেমন লাগে তা এখানে stack run। বিশেষত দ্রষ্টব্য যে এক্সিকিউটেবলটি বাশ শেলের সরাসরি শিশু। আরও সমালোচনামূলকভাবে, নোট করুন যে টার্মিনালের সম্মুখ পৃষ্ঠ প্রক্রিয়া গ্রুপ (টিপিজিআইডি) 17996, এবং সেই প্রক্রিয়া গোষ্ঠীর (পিজিআইডি) একমাত্র bracket-test-exeপ্রক্রিয়া প্রক্রিয়া।

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13816 13831 13831 13831 pts/3    17996 Ss    2001   0:00  |       \_ /bin/bash --noediting -i
13831 17996 17996 13831 pts/3    17996 Sl+   2001   0:00  |       |   \_ .../.stack-work/.../bracket-test-exe

ফলস্বরূপ, আপনি যখন stack runশেলের নীচে বা সরাসরি চলমান প্রক্রিয়াটিতে বাধা দিতে Ctrl-C টিপেন, তখন SIGINT সিগন্যালটি কেবলমাত্র bracket-test-exeপ্রক্রিয়াতে সরবরাহ করা হয়। এটি একটি তাত্পর্যপূর্ণ UserInterruptব্যতিক্রম উত্থাপন করে । পথ bracketকাজ করে, যখন:

bracket
  acquire
  (\() -> release)
  (\() -> body)

প্রক্রিয়া করার সময় একটি অ্যাসিনক্রোনাস ব্যতিক্রম প্রাপ্ত হয় body, এটি চলে releaseএবং তারপরে ব্যতিক্রমটি পুনরায় উত্থাপন করে। আপনার নেস্টেড bracketকলগুলির সাথে, এর প্রভাব রয়েছে অভ্যন্তরীণ দেহকে বাধাগ্রস্থ করতে, অভ্যন্তরীণ প্রকাশকে প্রক্রিয়াজাতকরণ, বাহ্যিক দেহকে ব্যাহত করার ব্যতিক্রমটিকে পুনরায় উত্থাপন এবং বহিরাগত রিলিজ প্রক্রিয়াজাতকরণ এবং শেষ পর্যন্ত প্রোগ্রামটি বন্ধ করার ব্যতিক্রমটি পুনরায় উত্থাপনের প্রভাব রয়েছে। (যদি bracketআপনার mainক্রিয়াকলাপের বাইরে আরও কিছু ক্রিয়া ঘটে থাকে তবে সেগুলি কার্যকর করা হত না))

অন্যদিকে, আপনি যখন ব্যবহার করবেন তখন stack testস্ট্যাকটি withProcessWaitপ্রক্রিয়াটির শিশু প্রক্রিয়া হিসাবে এক্সিকিউটেবল চালু করতে ব্যবহার stack testকরে। নিম্নলিখিত প্রক্রিয়া ট্রিতে, নোট করুন এটি bracket-test-testএকটি শিশু প্রক্রিয়া stack test। সমালোচনামূলকভাবে, টার্মিনালের অগ্রভাগ প্রক্রিয়া গ্রুপটি 18050 এবং সেই প্রক্রিয়া গোষ্ঠীতে stack testপ্রক্রিয়া এবং প্রক্রিয়া উভয়ই অন্তর্ভুক্ত bracket-test-test

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
13816 13831 13831 13831 pts/3    18050 Ss    2001   0:00  |       \_ /bin/bash --noediting -i
13831 18050 18050 13831 pts/3    18050 Sl+   2001   0:00  |       |   \_ stack test
18050 18060 18050 13831 pts/3    18050 Sl+   2001   0:00  |       |       \_ .../.stack-work/.../bracket-test-test

যখন আপনি টার্মিনালে Ctrl-C, আঘাত SIGINT সংকেত পাঠানো হয় সব উভয় তাই টার্মিনালের ফোরগ্রাউন্ড প্রক্রিয়া দলের প্রসেস stack testএবং bracket-test-testসংকেত পেতে। bracket-test-testউপরে বর্ণিত হিসাবে সিগন্যাল প্রক্রিয়াজাতকরণ এবং চূড়ান্তকরণ চালানো শুরু করবে। যাইহোক, এখানে একটি রেসের শর্ত রয়েছে কারণ যখন stack testবাধা দেওয়া হয় তখন এর মাঝখানে থাকে withProcessWaitযা আরও কম সংজ্ঞায়িত হিসাবে নিম্নরূপ:

withProcessWait config f =
  bracket
    (startProcess config)
    stopProcess
    (\p -> f p <* waitExitCode p)

সুতরাং, যখন এটির bracketব্যত্যয় stopProcessঘটে তখন এটি কল করে যা SIGTERMসংকেত প্রেরণ করে শিশু প্রক্রিয়াটি বন্ধ করে দেয় । বিপরীতে SIGINT, এটি একটি অ্যাসিনক্রোনাস ব্যতিক্রম বাড়ায় না। এটি কেবল কোনও ফাইনালাইজার চালানো শেষ করার আগেই শিশুটিকে তাত্ক্ষণিকভাবে সমাপ্ত করে।

আমি এটিকে ঘিরে কাজ করার কোনও বিশেষ উপায় সম্পর্কে ভাবতে পারি না। একটি উপায় হ'ল System.Posixপ্রক্রিয়াটিকে তার নিজস্ব প্রক্রিয়া গোষ্ঠীতে রাখার জন্য সুবিধাগুলি ব্যবহার করা:

main :: IO ()
main = do
  -- save old terminal foreground process group
  oldpgid <- getTerminalProcessGroupID (Fd 2)
  -- get our PID
  mypid <- getProcessID
  let -- put us in our own foreground process group
      handleInt  = setTerminalProcessGroupID (Fd 2) mypid >> createProcessGroupFor mypid
      -- restore the old foreground process gorup
      releaseInt = setTerminalProcessGroupID (Fd 2) oldpgid
  bracket
    (handleInt >> putStrLn "acquire")
    (\() -> threadDelay 1000000 >> putStrLn "release" >> releaseInt)
    (\() -> putStrLn "between" >> threadDelay 60000000)
  putStrLn "finished"

এখন, সিটিআরএল-সি কেবলমাত্র bracket-test-testপ্রক্রিয়াতে সাইনট সরবরাহ করা হবে । এটি পরিষ্কার হয়ে যাবে, প্রক্রিয়াটির দিকে নির্দেশ করার জন্য মূল অগ্রভাগ প্রক্রিয়া গোষ্ঠীটি পুনরুদ্ধার করবে stack testএবং সমাপ্ত হবে। এর ফলে পরীক্ষায় ব্যর্থতা stack testআসবে এবং চলমান থাকবে।

বিকল্পটি হ'ল হ্যান্ডেল করার চেষ্টা করা SIGTERMএবং শিশু প্রক্রিয়াটি ক্লিনআপ সঞ্চালনের জন্য চলমান রাখা, এমনকি stack testপ্রক্রিয়াটি শেষ হয়ে গেলেও । এটি শাল প্রম্পটটির দিকে তাকানোর সময় প্রক্রিয়াটি ধরণের পটভূমিতে পরিষ্কার হয়ে যাওয়ার কারণ এটি একধরণের কুৎসিত।


বিস্তারিত উত্তর দেওয়ার জন্য ধন্যবাদ! এফওয়াইআই আমি এটি সম্পর্কে একটি স্ট্যাক বাগ ফাইল করেছি : github.com/commercialhaskell/stack/issues/5144 । দেখে মনে হচ্ছে আসল ফিক্সটি বিকল্পগুলি (বা অনুরূপ কিছু) থেকে stack testপ্রক্রিয়া চালু করতে হবে । delegate_ctlcSystem.Process
tom
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.