Referencer og fejlhåndtering

MCOP-referencer er et af de mest centrale begreber i MCOP programmering. Dette afsnit forsøger at beskrive nøjagtigt hvordan referencer bruges, og behandler især fejltilfælde (server bryder sammen).

Grundlæggende egenskaber for referencer

  • En MCOP reference er ikke et objekt, men en reference til et objekt: Selv om følgende deklaration

       Arts::Synth_PLAY p;
    
    ser ud som en definition af et objekt, så deklarerer den kun en reference til et objekt. Som C++ programmør, kan du også se den som Synth_PLAY *, en slags peger til et Synth_PLAY-objekt. Det betyder specielt at p kan være det samme som en NULL-peger.

  • Du kan oprette en NULL-reference ved eksplicit at tildele den.

    Arts::Synth_PLAY p = Arts::Synth_PLAY::null();
    
  • At kalde objekter med en NULL-reference forårsager et hukommelsesdump

    Arts::Synth_PLAY p = Arts::Synth_PLAY::null();
       string s = p.toString();
    

    forårsager et hukommelsesdump. Hvis man sammenligner dette med en peger, er det stort set det samme som

       QWindow* w = 0;
       w->show();
    
    hvilket enhver C++ programmør ved at man skal undgå.

  • Uinitierede objekter forsøger at oprette sig selv når de først bruges

    Arts::Synth_PLAY p;
       string s = p.toString();
    

    er noget anderledes end at følge en NULL-peger. Du fortalte slet ikke objektet hvad det er, og nu forsøger du at bruge det. Gætværket her er at du vil have en ny lokal instans af et Arts::Synth_PLAY-objekt. Du kan naturligvis have villet gøre noget andet (såsom at oprette objektet et andet sted, eller bruge et eksisterende fjernobjekt). Det er i alle tilfælde en bekvem genvej til at oprette objekter. At oprette et objekt når det først bruges virker ikke når du allerede har tildelt det til noget andet (som en null-reference).

    Den tilsvarende C++ terminologi ville være

       QWidget* w;
       w->show();
    
    som naturligvis helt enkelt giver en segmenteringsfejl i C++. Så dette er anderledes her. Denne måde at oprette objekt er tricket, eftersom det ikke er nødvendigt at der findes en implementering for din grænseflade.

    Betragt for eksempel et abstrakt objekt såsom et Arts::PlayObject. Der er naturligvis konkrete PlayObjects, såsom de til for at afspille mp3-filer eller wav-filer, men

       Arts::PlayObject po;
       po.play();
    
    mislykkes helt sikkert. Problemet er at selvom et PlayObject forsøges at blive lavet, så mislykkes det eftersom der kun er objekter såsom Arts::WavPlayObject og lignende. Brug derfor kun denne måde at oprette objekter hvis du er sikker på at der er en implementering.

  • Referencer kan pege på samme objekt

    Arts::SimpleSoundServer s = Arts::Reference("global:Arts_SimpleSoundServer");
       Arts::SimpleSoundServer s2 = s;
    

    laver to referencer som angiver samme objekt. Det kopierer ikke nogen værdi, og laver ikke to objekter.

  • Alle objekter referenceregnes. Så snart et objekt ikke har nogen referencer længere, slettes det. Der er ingen måde udtrykkeligt at fjerne et objekt, men du kan dog bruge noget sådant her

       Arts::Synth_PLAY p;
       p.start();
       [...]
       p = Arts::Synth_PLAY::null();
    
    for at få Synth_PLAY-objektet til at forsvinde til slut. Specielt er det aldrig nødvendigt at bruge new og delete i sammenhæng med referencer.

Tilfældet hvor det mislykkes

Eftersom referencer kan pege på fjernobjekter, kan serverne som indeholder disse objekter bryde sammen. Hvad sker så?

  • Et sammenbrud ændrer ikke om en reference er en null-reference. Dette betyder at hvis foo.isNull() var true inden et serversammenbrud er den også true efter et serversammenbrud (hvilket er indlysende). Det betyder også at hvis foo.isNull() var false inden et serversammenbrud (foo angav et objekt) er den også false efter serversammenbruddet.

  • At kalde metoder med en gyldig reference forbliver sikkert. Antag at serveren som indeholder objektet calc brød sammen. Kald til objekter såsom

       int k = calc.subtract(i,j)
    
    er stadigvæk sikre. Det er åbenbart at subtract skal returnere noget, hvilket den ikke kan eftersom fjernobjektet ikke længere findes. I dette tilfælde ville (k == 0) være sand. I almindelighed forsøger operationer at returnere noget “neutralt” som resultat, såsom 0.0, en null-reference for objekter eller tomme strenge, når objektet ikke længere findes.

  • Kontrol med error() afslører om noget virkede.

    I ovenstående tilfælde, ville

       int k = calc.subtract(i,j)
       if(k.error()) {
          printf("k er ikke i-j!\n");
       }
    
    udskrive k er ikke i-j når fjernkaldet ikke virkede. Ellers er k virkelig resultatet af subtraktionsoperationen som udføres af fjernobjektet (intet serversammenbrud). For metoder som gør ting såsom at fjerne en fil, kan du ikke vide med sikkerhed om det virkelig er sket. Naturligvis skete det hvis .error() er false. Men hvis .error() er true, er der to muligheder:

    • Filen blev slettet, og serveren brød sammen præcis efter den blev slette, men inden resultatet overførtes.

    • Serveren brød sammen inden den kunne fjerne filen.

  • Brug af indlejrede kald er farligt i et program som skal være sikkert mod sammenbrud.

    Brug af noget i retning af

       window.titlebar().setTitle("foo");
    
    er ikke en god idé. Antag at du ved at vinduet indeholder en gyldig vinduesreference. Antag at du ved at window.titlebar() returnerer en reference til navnelisten eftersom vinduesobjektet er rigtigt implementeret. Sætningen ovenfor er imidlertid alligevel ikke sikker.

    Hvad kan ske hvis serveren som indeholder vinduesobjektet er brudt sammen. Så vil du, uafhængig af hvor god implementeringen af Window er, få en null-reference som resultat af operationen window.titlebar(). Og derefter vil kaldet til setTitle med denne null-reference naturligvis også føre til et sammenbrud.

    Så en sikker variant af dette ville være

       Titlebar titlebar = window.titlebar();
       if(!window.error())
          titlebar.setTitle("foo");
    
    og tilføj den rigtige fejlhåndtering hvis du vil. Hvis du ikke stoler på implementeringen af Window, kan du lige så godt bruge
       Titlebar titlebar = window.titlebar();
       if(!titlebar.isNull())
          titlebar.setTitle("foo");
    
    som begge er sikre.

Der er andre fejlbetingelser, såsom nedkobling af netværket (antag at du tager kablet mellem din server og klient væk mens dit program kører). Deres effekt er imidlertid den samme som et serversammenbrud.

Totalt set er det naturligvis et policy-spørgsmål hvor strengt du forsøger at håndtere kommunikationsfejl i hele dit program. Du kan følge metoden “hvis serveren bryder sammen, skal vi fejlsøge den til den aldrig bryder sammen igen”, som ville betyde at du ikke behøver bryde dig om alle disse problemer.

Interne funktioner: distribueret referenceregning

Et objekt skal ejes af nogen for at eksistere. Hvis det ikke gør det, vil det ophøre med at eksistere (mere eller mindre) med det samme. Internt angives en ejer ved at kalde _copy(), som forøger en reference tæller, og en ejer fjernes ved at kalde _release(). Så snart referencetælleren når nul, så slettes objektet.

Som en variation på temaet, angives fjernbrug med _useRemote(), og opløses med _releaseRemote(). Disse funktioner har en liste over hvilken server som har kaldt dem (og derfor ejer objektet). Dette bruges hvis serveren kobler ned (dvs. sammenbrud, netværksfejl), for at fjerne referencer som stadigvæk findes til objektet. Dette gøres i _disconnectRemote().

Nu er der et problem. Betragt en returværdi. I almindelige tilfælde ejes returværdiobjektet ikke af funktionen som kaldes længere. Det ejes heller ikke af den som kalder, førend meddelelsen som indeholder objektet er modtaget. Så der er en tid med objekter som “mangler ejere”.

Når vi nu sender et objekt kan man være rimeligt sikker på at så snart det modtages, ejes det af nogen igen, med mindre, igen, modtageren bryder sammen. Dette betyder i alle tilfælde at specielle hensyn skal tages for objekter i det mindste mens der sendes, og formodentlig også mens der modtages, så de ikke fjernes med det samme.

Måden som MCOP gør dette er ved at “mærke” objekter som er ved at blive kopieret over netværket. Inden en sådan kopiering begynder, kaldes _copyRemote. Dette forhindrer at objektet fjernes et stykke tid (5 sekunder). Så snart modtageren kalder _useRemote(), fjernes mærket igen. Så alle objekter som sendes over netværket, mærkes inden overførslen.

Hvis modtageren modtager et objekt som findes på samme server, så bruges _useRemote() naturligvis ikke. I dette specialtilfælde, findes funktionen _cancelCopyRemote() til at fjerne mærket manuelt. Foruden dette, er der også en tidsbaseret fjernelse af mærker, hvis mærkning udførtes, men modtageren ikke virkelig fik objektet (på grund af sammenbrud, netværksfejl). Dette gøres med klassen ReferenceClean.