Cascaded Initialisation
From ByteWiki
Contents |
Introduction
Straight from Zooba's Clog
"There is (at least) one common coding pattern which benefits greatly from the use of GOTO. I’ve
titled it, Cascaded Initialisation.
Take, for example, a function which intialises a system. There are 2 parts of this system and part A
must be initialised first. If part B fails to initialise then part A must be uninitialised."
Points to consider
- What happens on exception on init?
Zooba - Return value is false and you must uninitialise everything so far
- What happens on exception on uninit
Zooba - Assume the OS will handle it
- Why am I spending too much time on this?
Solutions
Zooba's solution
. ret = init_A(); if(!ret) goto uninit_none; ret = init_B(); if(!ret) goto uninit_A; ret = init_C(); if(!ret) goto uninit_B; ret = init_D(); if(!ret) goto uninit_C; return(SUCCESS); uninit_C: uninit_C(); uninit_B: uninit_B(); uninit_A: uninit_A(); uninit_none: return(FAILED);
This is used frequently in code which is careful about resource usage. The Linux kernel and samba certainly do this all the time. Jnewbigin
Xavier's solution
i = 0 objects = [a, b, c, d] success = true # Init objects for i in (0..objects.length-1) ret = objects[i].init if !ret success = false break end end # Uninit objects if anything failed if !success && i > 0 for j in (i-1..0) objects[i].uninit end end return success
pimaster's .NET solution
Sorry to take up so much space, but this is really a problem that could use a slightly more heavy weight approach.
namespace Initialisable { /// <summary> /// The interface for an object that starts the work. /// </summary> public interface IInitialiser { void Add(IInitialisable thing); bool Initialise(); } }
namespace Initialisable { /// <summary> /// An Object that can be started and rolledback /// </summary> public interface IInitialisable { bool Initialise(); void Rollback(); } }
using System; namespace Initialisable { /// <summary> /// Single Threaded Initialiser /// </summary> public class PlainInitialiser : IInitialiser { private bool _Initialised = false; // don't init twice? public PlainInitialiser() { } public void Add(IInitialisable thing) { if(! _Initialised ) { _Items.Add(thing); } else { Initialiser"); } } /// <summary> /// Start the chain reaction /// </summary> /// <returns>True if everything started ok</returns> public bool Initialise() { bool result = false; if( ! _Initialised ) { int i = 0; bool initOK = true; try { while( initOK && i < _Items.Count ) { IInitialisable initer = _Items[i] as IInitialisable; try { initOK = initer.Initialise(); } catch { initOK = false; throw; } if( initOK ) { i++; } } result = initOK; } finally { while( ! initOK && i >= 0 ) { try { IInitialisable initer = _Items[i] as IInitialisable; initer.Rollback(); } catch { // Implies all exceptions are ignored. } i--; } } } return result; } } }
using System; namespace Initialisable.Test { /// <summary> /// Summary description for OKInitialiser. /// </summary> public class OKInitialiser : IInitialisable { private string _Name; public OKInitialiser(string name) { _Name = name; } #region IInitialisable Members public bool Initialise() { try { System.Threading.Thread.Sleep(500); } catch(System.Threading.ThreadInterruptedException) {} // who cares Console.WriteLine("Started {0}", _Name); return true; } public void Rollback() { Console.WriteLine("Rollback {0}", _Name); } #endregion } }
using System; namespace Initialisable.Test { /// <summary> /// Summary description for FailInitialiser. /// </summary> public class FailInitialiser :IInitialisable { private string _Name; public FailInitialiser(string name) { _Name = name; } #region IInitialisable Members public bool Initialise() { try { System.Threading.Thread.Sleep(500); } catch(System.Threading.ThreadInterruptedException) {} // who cares Console.WriteLine("Started failure for {0}", _Name); return false; } public void Rollback() { Console.WriteLine("Rollback failure for {0}", _Name); } #endregion } }
using System; namespace Initialisable.Test { /// <summary> /// Summary description for ExceptionInitialiser. /// </summary> public class ExceptionInitialiser : IInitialisable { private string _Name; public ExceptionInitialiser(string name) { _Name = name; } #region IInitialisable Members public bool Initialise() { try { System.Threading.Thread.Sleep(500); } catch(System.Threading.ThreadInterruptedException) {} // who cares Console.WriteLine("Throwing Exception for {0}", _Name); } public void Rollback() { Console.WriteLine("Rollback Exception for {0}", _Name); } #endregion } }
using System; namespace Initialisable.Test { public delegate bool Init(); public delegate void UnInit(); /// <summary> /// Might as well use some sytax sugar if putting on a demo. /// </summary> public class DelegatedInitialiser : IInitialisable { private Init _Init; private UnInit _UnInit; public DelegatedInitialiser(Init init, UnInit uninit) { _Init = init; _UnInit = uninit; } #region IInitialisable Members public bool Initialise() { return _Init(); } public void Rollback() { _UnInit(); } #endregion } }
using System; namespace ConsoleApplication1 { /// <summary> /// And of course the test, which isn't in Nunit because I don't think I have it installed /// </summary> class InitTest { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { try { i.OKTest(); i.FailTest(); i.ExceptionTest(); } catch { Console.WriteLine("Bad luck really?"); } } private void OKTest() { bool result = initer.Initialise(); Console.WriteLine("The test result was (0)", result); } private void FailTest() { bool result = initer.Initialise(); Console.WriteLine("The test result was (0)", result); } private void ExceptionTest() { bool result = initer.Initialise(); Console.WriteLine("The test result was (0)", result); } } }
pimaster's Java solution
Interfaces for main use
package byteclub.initialiser; public interface IInitialisable { void rollback(); }
package byteclub.initialiser; public interface IInitialiser { void add(IInitialisable thing); }
package byteclub.initialiser; public InitialiserException() { super(); } { super(message); } { super(message, inner); } { super(inner); } }
Implementation of the initialiser
package byteclub.initialiser.impl; import java.util.ArrayList; import java.util.logging.Logger; import byteclub.initialiser.IInitialisable; import byteclub.initialiser.IInitialiser; import byteclub.initialiser.InitialiserException; public class PlainInitialiser implements IInitialiser { private boolean initialised = false; public void add(IInitialisable thing) { if( ! initialised ) { items.add(thing); } else { throw new InitialiserException("Cannot add new objects to a started Initialiser"); } } boolean result = false; if( ! initialised ) { int i = 0; boolean initOK = true; try { while( initOK && i < items.size() ) { initOK = false; initOK = initialise((IInitialisable)items.get(i)); if( initOK ) { i++; } } result = initOK; } finally { i--; // We do not roll back the one that failed. while( ! initOK && i >= 0 ) { rollback((IInitialisable)items.get(i)); i--; } } } return result; } Logger.getAnonymousLogger().finer("Doing step {" + initialisable + "}"); return initialisable.initialise(); } private void rollback(IInitialisable initialisable) { Logger.getAnonymousLogger().fine("Rolling back step {" + initialisable + "}"); try { initialisable.rollback(); } { Logger.getAnonymousLogger().warning("Roll back failed on " + initialisable + " with error " + e.getMessage()); } } }
Something with methods to call
package byteclub.initialiser.example; import java.io.File; public class WindowsMock { { return "I am the Process Heap"; } { RandomCrash(); return someObject.getClass(); }