Safari ના JavaScript engine માં એક છુપાયેલ ખામી છે: જ્યારે module-style Web Worker ની entry script બંડલના અન્ય કોઈ ભાગમાં import કરવામાં આવે છે, ત્યારે Safari તે entry script ને બીજી વાર એક્ઝિક્યુટ કરે છે. આ ડુપ્લીકેટ રન singleton state ને તોડી નાખે છે, જેનાથી shared caches અથવા single-instance objects પર આધાર રાખતા workers ને ખબર પણ ન પડે તે રીતે નુકસાન પહોંચાડે છે.

આ સમસ્યા ત્યારે સામે આવી જ્યારે અમે ProRes ફાઇલોને decode કરવા માટે Web Workers નો ઉપયોગ કરતી એક બ્રાઉઝર-આધારિત વિડિયો-પ્રોસેસિંગ એપ બનાવી રહ્યા હતા. Chrome અને Firefox એ કોઈ સમસ્યા વગર કોડ હેન્ડલ કર્યો, પરંતુ Safari માં વિડિયો લોડ કરવામાં સતત નિષ્ફળતા મળી રહી હતી. કન્સોલમાં માત્ર એક સામાન્ય “cannot read video” એરર દેખાતી હતી, જ્યારે અસલી કારણ worker નો initialization કોડ બે વાર રન થતો હતો અને મેમરીમાં તે જ module ની બે સ્વતંત્ર નકલો છોડી દેતો હતો.

How the bug manifests

આધુનિક bundlers (Vite, Rollup, વગેરે) ઘણીવાર shared utilities ને worker ની entry file માં લાવે છે જેથી lazy-loaded chunks તે કોડને entry point પરથી ફરીથી import કરી શકે. જે બ્રાઉઝર્સ પ્રમાણભૂત module loader વર્તણૂકનું પાલન કરે છે, તેમાં એકવાર entry module instantiate થઈ જાય પછી, loader કોઈપણ પછીના import માટે તે જ module object રિટર્ન કરે છે, જે બીજી વાર એક્ઝિક્યુશન અટકાવે છે.

Safari આ અપેક્ષાથી અલગ પડે છે. જ્યારે કોઈ lazy-loaded chunk worker ની entry file ને import કરે છે, ત્યારે Safari તે import ને એક નવી module request તરીકે ગણે છે અને entry script ને ફરીથી એક્ઝિક્યુટ કરે છે. પરિણામે ત્યાં વ્યાખ્યાયિત કરેલા દરેક variable, class, અથવા singleton ના બે અલગ-અલગ instances બને છે.

What breaks when the entry runs twice

  • Singletons અને caches હવે ડેટા શેર કરતા નથી; એક કોપી ખાલી cache જુએ છે જ્યારે બીજી તેને ભરે છે.
  • Registries (ઉદાહરણ તરીકે, message handlers ની યાદી) બંને instances વચ્ચે વહેંચાઈ જાય છે, જેનાથી એક બાજુ અસરકારક રીતે ખાલી રહે છે.
  • Event listeners બે વાર જોડાઈ જાય છે, જેનાથી ડુપ્લીકેટ હેન્ડલિંગ અથવા મેમરી વધવાની (memory bloat) શક્યતા રહે છે.
  • WebAssembly (WASM) modules બે વાર લોડ થાય છે, જેનાથી bandwidth અને initialization સમયનો બગાડ થાય છે.
  • આ નિષ્ફળતા શાંત છે: કોઈ uncaught exception થતું નથી, માત્ર જે લોજિક ખૂટતી state પર આધારિત છે તે ખોટું વર્તન કરે છે.

Detecting the issue in a project

બિલ્ટ એસેટ્સ (built assets) સામે ઝડપી grep કરવાથી જાણી શકાય છે કે કોઈ chunk worker ની entry file ને import કરે છે કે નહીં:

grep -l 'from"./your.worker-' dist/assets/*.js

જો કમાન્ડ કોઈપણ ફાઇલોની યાદી આપે છે, તો તે imports સંભવતઃ Safari માં double-run bug ને ટ્રિગર કરી રહ્યા છે.

Practical workarounds

  1. Worker entry માંથી shared code અલગ કરો.
    Bundler ને એવી રીતે કોન્ફિગર કરો કે સામાન્ય લાઇબ્રેરીઓને તેમના પોતાના chunk માં મૂકવામાં આવે (દા.ત., Rollup ના manualChunks નો ઉપયોગ કરીને). ત્યારબાદ worker અને કોઈપણ lazy-loaded modules તે ત્રીજી ફાઇલમાંથી લાઇબ્રેરીને import કરશે, જેનાથી worker ની entry point ને import કરવાની જરૂરિયાત દૂર થશે.

  2. એક 'thin' entry file નો ઉપયોગ કરો.
    Worker ની entry script ને માત્ર એક લાઇન સુધી મર્યાદિત કરો જે અસલી implementation ને re-export કરે છે:

    // worker-entry.js
    import("./main.js");
    

    જ્યાં સુધી અન્ય કોઈ બંડલ worker-entry.js ને import ન કરે, ત્યાં સુધી Safari ક્યારેય બીજી import request જોશે નહીં, તેથી entry માત્ર એક જ વાર રન થશે.

બંને અભિગમો સમગ્ર એપ્લિકેશનમાં worker ના initialization કોડને singleton રાખે છે.

Why the bug matters

Web Workers એ ભારે કમ્પ્યુટેશન—વિડિયો એન્કોડિંગ, ઇમેજ પ્રોસેસિંગ, ક્રિપ્ટોગ્રાફી—ને main thread થી દૂર લઈ જવા માટે એક સામાન્ય પેટર્ન છે. એક શાંત state split એક સંપૂર્ણ કાર્યક્ષમ ફીચરને સમયાંતરે થતી નિષ્ફળતામાં બદલી શકે છે જે ફક્ત Safari માં જ દેખાય છે, જે ડેસ્કટોપ અને મોબાઈલ ઉપકરણોના મોટા હિસ્સા પર ડિફોલ્ટ બ્રાઉઝર છે. કારણ કે એરર એક સામાન્ય media-load નિષ્ફળતા તરીકે દેખાય છે, ડેવલપર્સ ખોટા લક્ષણો પાછળ કલાકો બગાડી શકે છે.

આ બગ એક વ્યાપક જોખમને પણ પ્રકાશિત કરે છે: એવા module-loader semantics પર આધાર રાખવું જે બ્રાઉઝર્સમાં સમાન રીતે અમલમાં મૂકવામાં આવ્યા નથી. જ્યારે bundler ની optimization વ્યૂહરચના એક સિંગલ shared module instance ધારી લે છે, ત્યારે કોઈપણ વિચલન તે ધારણાને તોડી શકે છે.

Counter-point and open questions

Safari નું વર્તન તેના પોતાના module resolution નિયમો સાથે સુસંગત છે, જે workers ને લગતા edge cases માં spec થી થોડા અલગ પડે છે. કેટલાક ડેવલપર્સ દલીલ કરે છે કે bundlers એ worker ની entry file માં shared code રાખવાનું સંપૂર્ણપણે ટાળવું જોઈએ, જેનાથી આ સમસ્યા બ્રાઉઝરની ખામીને બદલે build-time શિસ્તનો વિષય બની જાય છે. અન્ય લોકો નિર્દેશ કરે છે કે Safari નું આ વિચલન દસ્તાવેજીકૃત (documented) નથી, જેના કારણે ડેવલપર્સ પાસે તેની અપેક્ષા રાખવા માટે કોઈ વિશ્વસનીય રીત નથી.

Apple એ આ સમસ્યાને જાહેરમાં સ્વીકારી નથી, અને તેને સુધારવા માટે કોઈ સમયરેખા જાણીતી નથી. જ્યાં સુધી Safari તેના loader માં ફેરફાર ન કરે ત્યાં સુધી, ડેવલપર્સ પર તેમના બંડલ્સને પુનર્ગઠિત કરવાની અથવા તેમના CI pipelines માં detection logic ઉમેરવાની જવાબદારી રહે છે.

What to watch next

  • બ્રાઉઝર અપડેટ્સ – module-worker હેન્ડલિંગના કોઈપણ ઉલ્લેખ માટે Safari રિલીઝ નોટ્સ પર નજર રાખો.
  • બંડલર કોમ્યુનિટી પેચિસ – Vite, Rollup અને અન્ય સાધનો આ બગને ટ્રિગર કરતા પેટર્નને ટાળવા માટે ચેતવણીઓ અથવા ઓટોમેટિક ચંકિંગ વ્યૂહરચનાઓ રજૂ કરી શકે છે.
  • ટેસ્ટિંગ પદ્ધતિઓ – રિલીઝ કરતા પહેલા Safari પર વાસ્તવિક મીડિયા ફાઇલો અને ફૂલ-સ્ટેક વર્કર ટેસ્ટનો સમાવેશ કરવાથી આ સાયલન્ટ ફેઈલ્યોર વહેલી તકે પકડી શકાય છે.

મુખ્ય સારાંશ

જો તમારા Safari વપરાશકર્તાઓ સમજાવી ન શકાય તેવી વર્કર-સંબંધિત નિષ્ફળતાઓ અનુભવે છે, તો તપાસો કે કોઈ નોન-વર્કર બંડલ વર્કરના એન્ટ્રી સ્ક્રિપ્ટને ઇમ્પોર્ટ તો નથી કરતું ને. ડબલ એક્ઝિક્યુશન બગ સાયલન્ટલી singleton state ને તોડી નાખે છે, પરંતુ શેર કરેલા કોડને એન્ટ્રી પોઈન્ટમાંથી બહાર ખસેડવાથી અથવા એન્ટ્રીને પાતળા re-export માં બદલવાથી બ્રાઉઝર ફિક્સની રાહ જોયા વિના સાચું વર્તન પુનઃસ્થાપિત કરી શકાય છે.