Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

#!/usr/bin/env python 

 

""" 

@file ion/util/state_object.py 

@author Michael Meisinger 

@brief base classes for objects that are controlled by an underlying state machine 

""" 

 

from twisted.internet import defer 

from twisted.python import failure 

import traceback 

 

import ion.util.ionlog 

log = ion.util.ionlog.getLogger(__name__) 

 

from ion.util.fsm import FSM 

 

class Actionable(object): 

    """ 

    @brief Provides an object that supports the execution of actions as consequence 

        of FSM transitions. 

    """ 

 

    def _action(self, action, fsm): 

        """ 

        @brief Execute action identified by argument 

        @param action A str with the action to execute 

        @param fsm the FSM instance that triggered the action. 

        @retval Maybe a Deferred 

        """ 

        raise NotImplementedError("Not implemented") 

 

class StateObject(Actionable): 

    """ 

    @brief Base class for an object instance that has an underlying FSM that 

        determines which inputs are allowed at any given time; inputs trigger 

        actions as defined by the FSM. 

        This is the class that specialized classes inherit from. 

        The underlying FSM can be set explicitly. 

    """ 

 

    def __init__(self): 

        # The FSM instance 

        self.__fsm = None 

 

    # Function prefix _so is for StateObject control functions 

 

    def _so_set_fsm(self, fsm_inst): 

        """ 

        @brief Set the "engine" FSM that drives the calling of the _action functions 

        """ 

        assert not self.__fsm, "FSM already set" 

        assert isinstance(fsm_inst, FSM), "Given object not a FSM" 

        self.__fsm = fsm_inst 

 

    def _so_process(self, event, *args, **kwargs): 

        """ 

        @brief Trigger the FSM with an event. Leads to action functions being called. 

            The complication is to make it deferred and non-deferred compliant. 

            (We do not want to use maybeDeferred to stay non-deferred). 

            This results in 2 (proc Def/non-Def) x 2 (proc succeed/fail) x 

            2 (error Def/non-Def) x 2 (error succeed/fail) = 16 combinations. 

            The downside is now a dependency on Twisted. Alternative: subclass 

        @retval Maybe a Deferred, or result of FSM process 

        @todo Improve the error catching, forwarding and reporting 

        """ 

        assert self.__fsm, "FSM not set" 

        self.__fsm.input_args = args 

        self.__fsm.input_kwargs = kwargs 

        self.__fsm.error_cause = None 

        try: 

            old_state = self.__fsm.current_state 

            # This is the main invocation of the FSM. It will lead to calls to 

            # the _action function in normal configuration. 

            res = self.__fsm.process(event) 

 

            if isinstance(res, defer.Deferred): 

                d_post = defer.Deferred() 

                def _cb(result): 

                    # Process to NEXT state was successful (after Deferred) 

                    #log.debug("FSM post-state" + str(self._get_state())) 

                    d_post.callback(result) 

                def _err(result): 

                    # Process to NEXT state failed (after Deferred) -> forward to ERROR 

                    log.error("ERROR in StateObject process(event=%s), D:\n%s" % (event, result)) 

                    try: 

                        res1 = self._so_error(result) 

                        if isinstance(res1, defer.Deferred): 

                            # FSM error action deferred too 

                            def _cb1(result1): 

                                # Process to ERROR state was successful (after Deferred) 

                                d_post.errback(result) 

                            def _err1(result1): 

                                # Process to ERROR state failed (after Deferred) 

                                log.error("Subsequent ERROR in StateObject error(), D-D:\n%s" % (result1)) 

                                d_post.errback(result) 

                            res1.addCallbacks(_cb1,_err1) 

                        else: 

                            # Process to ERROR state was successful (no Deferred) 

                            d_post.errback(result) 

                    except Exception, ex: 

                        # Process to ERROR state failed (no Deferred) 

                        log.exception("Subsequent ERROR in StateObject error(), D-ND") 

                        d_post.errback(result) 

                res.addCallbacks(_cb,_err) 

                res = d_post 

            else: 

                # Process to NEXT state was successful (no Deferred) -- YAY! 

                pass 

 

        except StandardError, ex: 

            # Process to NEXT state failed (no Deferred) -> forward to ERROR 

            log.exception("ERROR in StateObject process(event=%s)" % (event)) 

            try: 

                res1 = self._so_error(ex) 

                if isinstance(res1, defer.Deferred): 

                    result = failure.Failure(ex) 

                    d_post = defer.Deferred() 

                    # FSM error action deferred 

                    def _cb1(result1): 

                        # Process to ERROR state was successful (after Deferred) 

                        d_post.errback(result) 

                    def _err1(result1): 

                        # Process to ERROR state failed (after Deferred) 

                        log.error("Subsequent ERROR in StateObject error(), ND-D:\n%s" % (result1)) 

                        d_post.errback(result) 

                    res1.addCallbacks(_cb1,_err1) 

                    res = d_post 

                else: 

                    raise ex 

            except Exception, ex1: 

                log.exception("Subsequent ERROR in StateObject error(), ND-ND") 

                raise ex 

 

        return res 

 

    def _so_error(self, *args, **kwargs): 

        """ 

        @brief Brings the StateObject explicitly into the error state, because 

            of some action error. 

        """ 

        error = args[0] if args else None 

        self.__fsm.error_cause = error 

 

        # Is it OK to override the original args? 

        self.__fsm.input_args = args 

        self.__fsm.input_kwargs = kwargs 

 

        return self.__fsm.process(BasicStates.E_ERROR) 

 

    def _action(self, action, fsm): 

        """ 

        Generic action function that invokes. 

        """ 

        func = getattr(self, action) 

        args = self.__fsm.input_args 

        kwargs = self.__fsm.input_kwargs 

        if action == BasicStates.E_ERROR: 

            res = func(self.__fsm.error_cause, *args, **kwargs) 

        else: 

            res = func(*args, **kwargs) 

        return res 

 

    def _so_transition(self): 

        """ 

        @brief To be called from a pre-action event handler to explicitly 

            transition the FSM's state and turn the handler into a post-action. 

        """ 

        assert self.__fsm, "FSM not set" 

        return self.__fsm._transition() 

 

    def _get_state(self): 

        assert self.__fsm, "FSM not set" 

        return self.__fsm.current_state 

 

class FSMFactory(object): 

    """ 

    A factory for FSMs to be used in StateObjects 

    """ 

 

    def _create_action_func(self, target, action): 

        """ 

        @retval a function with a closure with the action name 

        """ 

        def action_target(fsm): 

            return target(action, fsm) 

        return action_target 

 

    def create_fsm(self, target, memory=None): 

        """ 

        @param a StateObject that is the 

        @param memory a state vector. if None will be set to empty list 

        @retval basic FSM with initial state 'INIT' and no transitions, and an 

            empty list as state vector 

        """ 

        assert isinstance(target, Actionable) 

        memory = memory or [] 

        fsm = FSM('INIT', memory) 

        return fsm 

 

class BasicStates(object): 

    """ 

    @brief Defines constants for basic state and lifecycle FSMs. 

    """ 

    # States 

    # Note: The INIT state is active before the initialize input is received 

    S_INIT = "INIT" 

    S_READY = "READY" 

    S_ACTIVE = "ACTIVE" 

    S_TERMINATED = "TERMINATED" 

    S_ERROR = "ERROR" 

 

    # Input events 

    E_INITIALIZE = "initialize" 

    E_ACTIVATE = "activate" 

    E_DEACTIVATE = "deactivate" 

    E_TERMINATE = "terminate" 

    E_ERROR = "error" 

 

    # Actions - in general called the same as the triggering event 

    A_ACTIVE_TERMINATE = "terminate_active" 

 

class BasicFSMFactory(FSMFactory): 

    """ 

    A FSM factory for FSMs with basic state model. 

    """ 

 

    def _create_action_func(self, target, action): 

        """ 

        @retval a function with a closure with the action name 

        """ 

        def action_target(fsm): 

            return target("on_%s" % action, fsm) 

        return action_target 

 

    def create_fsm(self, target, memory=None): 

        fsm = FSMFactory.create_fsm(self, target, memory) 

 

        actf = target._action 

 

        actionfct = self._create_action_func(actf, BasicStates.E_INITIALIZE) 

        fsm.add_transition(BasicStates.E_INITIALIZE, BasicStates.S_INIT, actionfct, BasicStates.S_READY) 

 

        actionfct = self._create_action_func(actf, BasicStates.E_ACTIVATE) 

        fsm.add_transition(BasicStates.E_ACTIVATE, BasicStates.S_READY, actionfct, BasicStates.S_ACTIVE) 

 

        actionfct = self._create_action_func(actf, BasicStates.E_DEACTIVATE) 

        fsm.add_transition(BasicStates.E_DEACTIVATE, BasicStates.S_ACTIVE, actionfct, BasicStates.S_READY) 

 

        actionfct = self._create_action_func(actf, BasicStates.E_TERMINATE) 

        fsm.add_transition(BasicStates.E_TERMINATE, BasicStates.S_READY, actionfct, BasicStates.S_TERMINATED) 

 

        actionfct = self._create_action_func(actf, BasicStates.A_ACTIVE_TERMINATE) 

        fsm.add_transition(BasicStates.E_TERMINATE, BasicStates.S_ACTIVE, actionfct, BasicStates.S_TERMINATED) 

 

        actionfct = self._create_action_func(actf, BasicStates.E_ERROR) 

        fsm.set_default_transition(actionfct, BasicStates.S_ERROR) 

 

        return fsm 

 

class BasicLifecycleObject(StateObject): 

    """ 

    A StateObject with a basic life cycle, as determined by the BasicFSMFactory. 

    @see BasicFSMFactory 

    @todo Add precondition checker 

    """ 

 

    def __init__(self): 

        StateObject.__init__(self) 

        factory = BasicFSMFactory() 

        fsm = factory.create_fsm(self) 

        self._so_set_fsm(fsm) 

 

    def initialize(self, *args, **kwargs): 

        return self._so_process(BasicStates.E_INITIALIZE, *args, **kwargs) 

 

    def activate(self, *args, **kwargs): 

        return self._so_process(BasicStates.E_ACTIVATE, *args, **kwargs) 

 

    def deactivate(self, *args, **kwargs): 

        return self._so_process(BasicStates.E_DEACTIVATE, *args, **kwargs) 

 

    def terminate(self, *args, **kwargs): 

        return self._so_process(BasicStates.E_TERMINATE, *args, **kwargs) 

 

    def error(self, *args, **kwargs): 

        return self._so_process(BasicStates.E_ERROR, *args, **kwargs) 

 

    def on_initialize(self, *args, **kwargs): 

        raise NotImplementedError("Not implemented") 

 

    def on_activate(self, *args, **kwargs): 

        raise NotImplementedError("Not implemented") 

 

    def on_deactivate(self, *args, **kwargs): 

        raise NotImplementedError("Not implemented") 

 

    def on_terminate_active(self, *args, **kwargs): 

        """ 

        @brief this is a shorthand delegating to on_terminate from the ACTIVE 

            state. Subclasses can override this action handler with more specific 

            functionality 

        """ 

        return self.on_terminate(*args, **kwargs) 

 

    def on_terminate(self, *args, **kwargs): 

        raise NotImplementedError("Not implemented") 

 

    def on_error(self, *args, **kwargs): 

        raise NotImplementedError("Not implemented")