Professional Documents
Culture Documents
Accidental Complexities Low-level APIs Poor debugging tools Algorithmic decomposition Continuous re-invention/discovery of core concepts & components Inherent Complexities Latency Reliability Load balancing Causal ordering Scheduling & synchronization Deadlock
Presentation Outline
Cover OO techniques & language features that enhance software quality
Patterns, which embody reusable software architectures & designs ACE wrapper facades, which encapsulate OS concurrency & network programming APIs OO language features, e.g., classes, dynamic binding & inheritance, parameterized types Presentation Organization 1. Background 2. Concurrent & network challenges & solution approaches 3. Patterns & wrapper facades in ACE + applications
3
Features Open-source 200,000+ lines of C++ 40+ personyears of effort Ported to many OS platforms
Most versions of UNIX, e.g., SunOS 4.x and Solaris, SGI IRIX, HPUX, Digital UNIX (Compaq Tru64), AIX, DG/UX, SCO OpenServer, UnixWare, NetBSD, & FreeBSD;
Real-time operating systems, e.g., VxWorks, OS/9, Chorus, LynxOS, Pharlap TNT, QNX Neutrino and RTP, RTEMS, & pSoS; Large enterprise systems, e.g., OpenVMS, MVS OpenEdition, Tandem NonStop-UX, & Cray UNICOS ACE can be used with all of the major C++ compilers on these platforms
The ACE Web site at http://www.cs.wustl.edu/~schmidt/ACE.html contains a complete, up-to-date list of platforms, along with instructions for downloading & building ACE
15
Concurrency
Synchronization
16
17
Inversion of Control Calls back to application-supplied event handlers to perform processing when events occur synchronously & asynchronously Calls back to application-supplied service objects to initialize, suspend, resume, & finalize them Calls back to an application-supplied hook method to perform processing in one or more threads of control Calls back to service handlers to initialize them after they are connected
Streams
20
Calls back to initialize & finalize tasks when they are pushed & popped from a stream
The logging server example in C++NPv2 is more sophisticated than the one in C++NPv1 Theres an extra daemon involved
26
Active Object
Reactor
Strategized Locking
Scoped Locking
Thread-safe Interface
27
%T
29
Logging Severities
You can control which severities are seen at run time Two masks determine whether a message is displayed: Process-wide mask (defaults to all severities enabled) Per-thread mask (defaults to all severities disabled) If logged severity is enabled in either mask, message is displayed Set process/instance mask with:
ACE_Log_Msg::priority_mask (u_long mask, MASK_TYPE which);
Since default is to enable all severities process-wide, all severities are logged in all threads unless you change it Per-thread mask initializer can be adjusted (default is all severities disabled):
ACE_Log_Msg::disable_debug_messages (); ACE_Log_Msg::enable_debug_messages();
Any set of severities can be specified (ORd together) Note that these methods set and clear a (set of) bits instead of replacing the mask, as priority_mask() does
30
LM_DEBUG severity (only) logged in service thread LM_DEBUG severity (and all others) not logged in worker threads Note that enable_debug_messages() & disable_debug_messages() are static methods
31
ACE_Log_Msg::open (const ACE_TCHAR *prog_name, u_long options_flags = ACE_Log_Msg::STDERR, const ACE_TCHAR *logger_key = 0);
ACE_Log_Msg::set_flags (u_long flags);
Assign a stream: ACE_Log_Msg::msg_ostream (ACE_OSTREAM_TYPE *); (Optional 2nd arg to tell ACE_Log_Msg to delete the ostream)
ACE_OSTREAM_TYPE is ofstream where supported, else FILE* To also stop output to stderr, use open() without STDERR flag, or ACE_Log_Msg::clr_flags (STDERR)
32
33
Logging Callbacks
Logging callbacks are useful for adding special processing or filtering to log output Derive a class from ACE_Log_Msg_Callback & reimplement: virtual void log (ACE_Log_Record &log_record); Use ACE_Log_Msg::msg_callback() to register callback Also call ACE_Log_Msg::set_flags() to add ACE_Log_Msg::MSG_CALLBACK flag Beware Callback registration is specific to each ACE_Log_Msg instance Callbacks are not inherited when new threads are created
34
35
Tracing
ACEs tracing facility logs function/method entry & exit Uses logging with severity LM_TRACE, so output can be selectively disabled Just put ACE_TRACE macro in the function: #include ace/Log_Msg.h void foo (void) { ACE_TRACE (foo); // do stuff } Says: (1024) Calling foo in file test.cpp on line 8 (1024) Leaving foo Clever indenting by call depth makes output easier to read Huge amount of output, so tracing no-opd out by default; rebuild with config.h having: #define ACE_NTRACE 0
36
37
Communication dimensions address the rules, form, & level of abstraction that networked applications use to interact Concurrency dimensions address the policies & mechanisms governing the proper use of processes & threads to represent multiple service instances, as well as how each service instance may use multiple threads internally
38
Service dimensions address key properties of a networked application service, such as the duration & structure of each service instance Configuration dimensions address how networked services are identified & the time at which they are bound together to form complete applications
Communication is fundamental to networked application design The next three slides present a domain analysis of communication design dimensions, which address the rules, form, and levels of abstraction that networked applications use to interact with each other We cover the following communication design dimensions: Connectionless versus connection-oriented protocols Synchronous versus asynchronous message exchange Message-passing versus shared memory
39
Connector
Acceptor
provide a reliable, sequences, nonduplicated delivery service, which is useful for applications that cant tolerate data loss Examples include TCP & ATM
a message-oriented service in which each message can be routed and delivered independently Examples include UDP & IP
Connection-oriented applications must address two additional design issues: Data framing strategies, e.g., bytestream vs. message-oriented Connection multiplexing (muxing) strategies, e.g., multiplexed vs. nonmultiplexed
40
In multiplexed connections all client requests emanating from threads in a single process pass through one TCP connection to a server process Pros: Conserves OS communication resources, such as socket handles and connection control blocks Cons: harder to program, less efficient, & less deterministic 41
In nonmultiplexed connections each client uses a different connection to communicate with a peer service Pros: Finer control of communication priorities & low synchronization overhead since additional locks aren't needed
Cons: use more OS resources, & therefore may not scale well in certain environments
These protocols therefore often require a strategy for detecting lost or failed requests & resending them later
Shared memory allows multiple processes on the same or different Application developers generally define the hosts to access & exchange data as though it were local to the address protocol for exchanging the data, e.g.: space of each process Format & content of the data Applications using native OS shared Number of possible participants in each memory mechanisms must define exchange (e.g., point-to-point unicast), how to locate & map the shared multicast, or broadcast) memory region(s) & the data How participants begin, conduct, & end structures that are placed in shared a message-passing session memory
43
void *obj_buf = // Get a pointer to location in shared memory ABC *abc = new (obj_buf) ABC; // Use C++ placement new operator
Pointer passed to placement new operator must point to a memory region that is big enough & is aligned properly for the object type being created
The placed object must be destroyed by explicitly calling the destructor Pitfalls initializing C++ objects with virtual functions in shared memory
The shared memory region may reside at a different virtual memory location in each process that maps the shared memory
The C++ compiler/linker need not locate the vtable at the same address in different processes that use the shared memory
ACE wrapper faade classes that can be initialized in shared memory must therefore be concrete data types
i.e., classes with only non-virtual methods
44
A socket can be bound to a local or remote address In Unix, socket handles & I/O handles can be used interchangeably in most cases, but this is not the case for Windows
Communication domain
e.g., local host only versus local or remote host
47
3
4 const int PORT_NUM = 10000; 5 6 int echo_server () 7 { 8 9 struct sockaddr_in addr; int addr_len; Forgot to initialize to sizeof (sockaddr_in)
10
11 12
char buf[BUFSIZ];
int n_handle; Use of non-portable handle type
50
19
20 21 22 23 24
51
while ((n = read (s_handle, buf, sizeof buf)) > 0) write (n_handle, buf, n); Reading from wrong handle
31
32 33 34 35 } } return 0;
52
53
Applications
Problem
The diversity of hardware & operating systems makes it hard to build portable & robust networked application software Programming directly to low-level OS APIs is tedious, error-prone, & non54 portable
Solaris VxWorks Win2K Linux LynxOS
API FunctionA()
Application
API FunctionB()
API FunctionC()
calls
This pattern encapsulates data & functions provided by existing non-OO APIs within more concise, robust, portable, maintainable, & cohesive OO class interfaces
55
: Application
: Wrapper Facade
method()
: APIFunctionA
: APIFunctionB
functionA() functionB()
ACE also has wrapper facades for datagrams e.g., unicast, multicast, broadcast
57
The active connection role (ACE_SOCK_Connector) is played by a peer application that initiates a connection to a remote peer The passive connection role (ACE_SOCK_Acceptor) is played by a peer application that accepts a connection from a remote peer & The communication role (ACE_SOCK_Stream) is played by both peer applications to exchange data after they are connected
To minimize the complexity of these low-level details, ACE defines a hierarchy of classes that provide a uniform interface for all ACE network addressing objects
58
Class Capabilities The ACE_Addr class is the root of the ACE network addressing hierarchy The ACE_INET_Addr class represents TCP/IP & UDP/IP addressing information This class eliminates many subtle sources of accidental complexity
59
Even the ACE_HANDLE typedef is still not sufficiently object-oriented & typesafe
int buggy_echo_server (u_short port_num) { sockaddr_in s_addr; int acceptor = socket (PF_UNIX, SOCK_DGRAM, 0); int is not portable to Windows s_addr.sin_family = AF_INET; s_addr.sin_port = port_num; s_addr.sin_addr.s_addr = INADDR_ANY; bind (acceptor, (sockaddr *) &s_addr, sizeof s_addr); int handle = accept (acceptor, 0, 0); for (;;) { char buf[BUFSIZ]; ssize_t n = read (acceptor, buf, sizeof buf); if (n <= 0) break; Reading from wrong handle write (handle, buf, n); } }
60
Create & destroy socket handles Obtain the network addresses of local & remote peers
Set/get socket options, such as socket queue sizes, Enable broadcast/multicast communication
61
63
64
65
// Designate a timed connect. ACE_Time_Value timeout (10); // 10 second timeout. if (connector.connect (peer, Perform a timed connect peer_addr, e.g., 10 seconds in this &timeout) == -1) { case if (errno == ETIME) { // Timeout, do something else 67
68
write on ACE_SOCK_Stream
for (ssize_t n; (n = peer.recv (buf, sizeof buf)) > 0; ) ACE::write_n (ACE_STDOUT, buf, n); return peer.close () == -1 ? 1 : 0;
Perform blocking read on
}
71
ACE_SOCK_Stream
74
}
76 }
return acceptor.close () == -1 ? 1 : 0;
connections
Many modern operating systems provide a mechanism for mapping a files contents directly into a processs virtual address space
This memory-mapped file mechanism can be read from or written to directly by referencing the virtual memory e.g., via pointers instead of using less efficient I/O functions
A wrapper faade that encapsulates the memory mapped file system mechanisms on different operating systems
Relieves application developers from having to manually perform bookkeeping tasks
The file manager defers all read/write operations to the virtual memory manager
Contents of memory mapped files can be shared by multiple processes on the same machine It can also be used to provide a persistent backing store 77
e.g., explicitly opening files or determining their lengths The ACE_Mem_Map class offers multiple constructors with several signature variants
Motivation Many networked applications require a means to manipulate messages efficiently, e.g.: Storing messages in buffers as they are received from the network or from other processes Adding/removing headers/trailers from messages as they pass through a user-level protocol stack Fragmenting/reassembling messages to fit into network MTUs Storing messages in buffers for transmission or retransmission Reordering messages that were received out-of-sequence 78
79
Simple messages contain a Composite messages contain multiple one ACE_Message_Block ACE_Message_Blocks These blocks are linked together in accordance with An ACE_Message_Block the Composite pattern points to an Composite messages often consist of a control ACE_Data_Block message that contains bookkeeping information An ACE_Data_Block e.g., destination addresses, followed by one or points to the actual data more data messages that contain the actual payload contents of the message ACE_Data_Blocks can be referenced counted
80
mblk->wr_ptr (nbytes);
81
chain it to the previous one at the end of the list mblk->cont (new ACE_Message_Block (BUFSIZ)); mblk = mblk->cont (); Advance mblk to point to the newly } allocated ACE_Message_Block
// Print the contents of the list to the standard output. for (mblk = head; mblk != 0; mblk = mblk->cont ()) ACE::write_n (ACE_STDOUT, mblk->rd_ptr (), mblk->length ());
For every message block, print mblk->length() amount
chain head->release (); // Release all the memory in the chain. return 0; }82
(De)marshaling
To interoperate with heterogeneous compiler alignments & hardware instructions with different byte-orders
The ACE_OutputCDR & ACE_InputCDR classes provide a highly optimized, portable, & convenient means to marshal & demarshal data using the standard CORBA Common Data Representation (CDR) ACE_OutputCDR creates a CDR buffer from a data structure (marshaling) 83 ACE_InputCDR extracts data from a CDR buffer (demarshaling)
They support zero copy marshaling & demarshaling of octet buffers Users can define custom character set translators for platforms that do not 84 use ASCII or Unicode as their native character sets
This example uses a 8-byte, CDR encoded header followed by the payload Header includes byte order, payload length, & other fields
Using ACE_OutputCDR
We show the ACE CDR insertion (operator<<) & extraction (operator>>) operators for ACE_Log_Record that's used by client application & logging server
int operator<< (ACE_OutputCDR &cdr, const ACE_Log_Record &log_record) { size_t msglen = log_record.msg_data_len (); // Insert each <log_record> field into the output CDR stream. cdr << ACE_CDR::Long (log_record.type ()); cdr << ACE_CDR::Long (log_record.pid ()); cdr << ACE_CDR::Long (log_record.time_stamp ().sec ()); cdr << ACE_CDR::Long (log_record.time_stamp ().usec ()); cdr << ACE_CDR::ULong (msglen); cdr.write_char_array (log_record.msg_data (), msglen); return cdr.good_bit (); }
86
After marshaling all the fields of the log record into the CDR stream, return the success/failure status
Using ACE_InputCDR
int operator>> (ACE_InputCDR &cdr, ACE_Log_Record &log_record) { ACE_CDR::Long type; Temporaries used during ACE_CDR::Long pid; demarshaling (not always ACE_CDR::Long sec, usec; necessary) ACE_CDR::ULong buffer_len; // Extract each field from input CDR stream into <log_record>. if ((cdr >> type) && (cdr >> pid) && (cdr >> sec) && (cdr >> usec) && (cdr >> buffer_len)) { ACE_TCHAR log_msg[ACE_Log_Record::MAXLOGMSGLEN + 1]; log_record.type (type); log_record.pid (pid); log_record.time_stamp (ACE_Time_Value (sec, usec)); cdr.read_char_array (log_msg, buffer_len); log_msg[buffer_len] = '\0'; log_record.msg_data (log_msg); } return cdr.good_bit (); After demarshaling all the fields of the log record from the CDR stream, return the success/failure } status
87
88
class Logging_Client { Header file: Logging_Client.h public: // Send <log_record> to the server. int send (const ACE_Log_Record &log_record); // Accessor method. ACE_SOCK_Stream &peer () { return logging_peer_; } // Close the connection to the server. ~Logging_Client () { logging_peer_.close (); } private: ACE_SOCK_Stream logging_peer_; // Connected to server. };
2.Marshals the header & data into an output CDR (lines 10 16) &
3.Sends it to the logging server (lines 18 24)
1 int Logging_Client::send (const ACE_Log_Record &log_record) { 2 const size_t max_payload_size = 3 4 // type() 4 + 8 // timestamp 5 + 4 // process id 6 + 4 // data length 7 + ACE_Log_Record::ACE_MAXLOGMSGLEN // data 8 + ACE_CDR::MAX_ALIGNMENT; // padding; 9 10 ACE_OutputCDR payload (max_payload_size); 11 payload << log_record; 12 ACE_CDR::ULong length = payload.total_length (); 13 First marshal the payload to contain the linearized ACE_Log_Record
89
Since TCP/IP is a bytestream protocol (i.e., without any message boundaries) the logging service uses CDR as a message framing protocol to delimit log records
Action
Displays the line number where the error occurred Displays the file name where the error occurred Displays the name of the program Displays the current process ID Takes a const char * argument and displays it and the error string corresponding to errno (similar to perror()) Displays the current time Displays the calling threads ID
Contents of the message to be sent to logging server are obtained from standard input
93
95
protected: // The following four methods are ``hooks'' that can be // overridden by subclasses. virtual int open (u_short logger_port = 0); virtual int wait_for_multiple_events () { return 0; } virtual int handle_connections () = 0; virtual int handle_data (ACE_SOCK_Stream * = 0) = 0;
96
// Close the socket endpoint and shutdown ACE. virtual ~Logging_Server () { acceptor_.close (); } // Accessor. ACE_SOCK_Acceptor &acceptor () { return acceptor_; }
private: // Socket acceptor endpoint. ACE_SOCK_Acceptor acceptor_; };
98
int Logging_Server::run (int argc, char *argv[]) { if (open (argc > 1 ? atoi (argv[1]) : 0) == -1) return -1; Three hook methods that can be overridden in subclasses for (;;) { if (wait_for_multiple_events () == -1) return -1; if (handle_connections () == -1) return -1; if (handle_data () == -1) return -1; } return 0; }
99
// Start listening and enable reuse of listen address // for quick restarts. return acceptor_.open (server_addr, 1);
100
The design of these wrapper facades is very similar to ACE IPC wrapper facades
The ACE File classes decouple: Initialization factories: e.g., ACE_FILE_Connector, which opens and/or creates files Data transfer classes: e.g., ACE_FILE_IO, which applications use to read/write data from/to files opened using ACE_FILE_Connector This generality in ACEs design of wrapper facades helps strategize higher-level ACE framework components e.g., ACE_Acceptor, ACE_Connector, & ACE_Svc_Handler
102
103
1 int Logging_Handler::recv_log_record (ACE_Message_Block *&mblk) 2 { First save the peer hostname 3 ACE_INET_Addr peer_addr; 4 logging_peer_.get_remote_addr (peer_addr); 5 mblk = new ACE_Message_Block (MAXHOSTNAMELEN + 1); 6 peer_addr.get_host_name (mblk->wr_ptr (), MAXHOSTNAMELEN); 7 mblk->wr_ptr (strlen (mblk->wr_ptr ()) + 1); // Go past name. 8 9 ACE_Message_Block *payload = 10 new ACE_Message_Block (ACE_DEFAULT_CDR_BUFSIZE); 11 // Align Message Block for a CDR stream. Force proper alignment 12 ACE_CDR::mb_align (payload); 13 14 if (logging_peer_.recv_n (payload->wr_ptr (), 8) == 8) { 15 payload->wr_ptr (8); // Reflect addition of 8 bytes. 16 Receive the header info (byte 17 ACE_InputCDR cdr (payload); order & length) 18 105
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 }
106
1 int Logging_Handler::write_log_record (ACE_Message_Block *mblk) 2 { 3 if (log_file_->send_n (mblk) == -1) return -1; 4 5 if (ACE::debug ()) { 6 ACE_InputCDR cdr (mblk->cont ()); 7 ACE_CDR::Boolean byte_order; 8 ACE_CDR::ULong length; 9 cdr >> ACE_InputCDR::to_boolean (byte_order); 10 cdr.reset_byte_order (byte_order); 11 cdr >> length; 12 ACE_Log_Record log_record; 13 cdr >> log_record; // Extract the <ACE_log_record>. 14 log_record.print (mblk->rd_ptr (), 1, cerr); 15 } 16 17 return mblk->total_length (); 18 } 107
Later on well see the virtue of splitting the recv_log_record() & write_log_record() logic into two methods
108
ACE_SOCK_Acceptor ACE_SOCK_Stream
if (acceptor ().accept (logging_handler_.peer (), &logging_peer_addr) == -1) ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "accept()"), -1); ACE_DEBUG ((LM_DEBUG, "Accepted connection from %s\n", logging_peer_addr.get_host_name ())); return 0; }
111
113
Iterative/reactive servers handle each client request in its entirety before servicing subsequent requests Best suited for short-duration or 114 infrequent services
Concurrent servers handle multiple requests from clients simultaneously Best suited for I/O-bound services or long-duration services
A process provides the context for executing program instructions Each process manages certain resources (such as virtual memory, I/O handles, and signal handlers) & is protected from other OS processes via an MMU IPC between processes can be complicated & inefficient
115
A thread is a sequence of instructions in the context of a process Each thread manages certain resources (such as runtime stack, registers, signal masks, priorities, & thread-specific data) Threads are not protected from other threads IPC between threads can be more efficient than IPC between processes
This pattern yields two primary benefits: 1.Threads can be mapped to separate CPUs to scale up server performance via multi-processing 2.Each thread blocks independently, which prevents a flowcontrolled connection from degrading the QoS that other clients receive
117
This pattern defines two service processing layersone async & one syncalong with a queueing layer that allows services to exchange messages between the two layers
118
The pattern allows sync services (such as processing log records from different clients) to run concurrently, relative both to each other & to async/reactive services (such as event demultiplexing)
Worker Thread 2
<<get>>
Worker Thread 3
<<get>>
Request Queue
<<put>>
handlers acceptor
Event source
Solution
Apply the Leader/Followers architectural pattern to minimize server threading overhead
119
*
Event Handler handle_event () get_handle()
Handle
*
Handle Set handle_events() deactivate_handle() reactivate_handle() select() Concrete Event Handler B handle_event () get_handle() Concrete Event Handler A handle_event () get_handle()
1.Leader thread demuxing 2.Follower thread promotion 3.Event handler demuxing & event processing 4.Rejoining the thread pool
121
thread 2 sleeps until it becomes the leader thread 2 waits for a new event, thread 1 processes current event join() thread 1 sleeps until it becomes the leader
handle_ events()
reactivate_ handle()
event
handle_event() deactivate_ handle()
The primary benefit of on-demand spawning strategies is their reduced consumption of resources The drawbacks, however, are that these strategies can degrade performance in heavily loaded servers & determinism in real-time systems due to costs of spawning processes/threads and starting services
122
123
Process contention scope (aka user threading) where threads in the same process compete with each other (but not directly with threads in other processes)
System contention scope (aka kernel threading) where threads compete directly with other system-scope threads, regardless of what process theyre in
When the OS kernel blocks an LWP, all user threads scheduled onto it by the threads library also block However, threads scheduled onto other LWPs in the process can continue to make progress
Multiprocessing
Multithreading & Synchronization mechanisms available to implement those designs We also discuss common portability & programming problems that arise when networked applications are developed using native C-level concurrency APIs
126
Multiprocessing Mechanisms
Multiprocessing mechanisms include the features provided by the OS for creating & managing the execution of multiple processes, e.g., Process lifetime operations such as fork() & exec*() on POSIX & CreateProcess() on Win32 create a new process address space for programs to run The initiating process can set command line arguments, environment variables and working directories for the new process The new process can terminate voluntarily by reaching the end of its execution or be involuntarily killed via signals (in POSIX) or TerminateProcess() (in Win32) Process synchronization options provided by the OS to retain the identity and exit status of a process and report it to the parent, e.g., POSIX wait() & waitpid() Win32 WaitForSingleObject() & WaitForMultipleObjects() Process property operations used to get/set process properties, such as default file access permissions, user identification, resource limits, scheduling priority and current working directory
128
Involuntarily by being killed via a signal or an aynchronous thread cancelation operations, such as pthread_cancel() (Pthreads) and TerminateThread() (Win32)
Thread synchronization operations that allow created threads to be Detached where the OS reclaims storage used for the threads state & exit status after it has exit Joinable where the OS retains identity & exit status of a terminating thread so other threads can synchronize with it Other operations that allow threads to suspend & resume each other, or send signals to other threads
129
Thread-specific storage is similar to global data except that the data is global in the scope of the executing thread.
Each thread has its own copy of a TSS data e.g., errno Each TSS item has a key that is global to all threads within a process A thread uses this key to access its copy of the TSS data Keys are created by factory functions, such as pthread_key_create() (Pthreads) or TlsAlloc() (Win32). Key/pointer relationships are managed by TSS set/get functions, such as pthread_getspecific() & pthread_setspecific() (Pthreads) or TlsGetValue() & TlsSetValue() (Win32)
130
131
Semaphores is a non negative integer that can be incremented and decremented atomically
A thread blocks when it tried to decrement a semaphore whose value is 0 A block thread makes progress when another thread increments the value of the semaphore Usually implemented using sleep locks, that trigger a context switch Condition variables allows a thread to coordinate & schedule its own processing A thread can wait on complex expressions to attain a desired state Used to build higher level patterns such as active object & monitor objects
132
133
134
135
136
137
138
140
if (select (active_handles_.max_set () + 1, active_handles_.fdset (), 0, // no write_fds 0, // no except_fds Use select() to determine 0) == -1) // no timeout all active handles return -1; active_handles_.sync ((ACE_HANDLE) active_handles_.max_set () + 1); return 0; sync() resets handle count in ACE_Handle_Set after the select() call }
Possible race condition due to asynchronous behavior of TCP/IP leading to accept() call being blocked forever & hanging the application
Solution Acceptor sockets should always be set in non-blocking mode Achieved portably in ACE via the enable()method of ACE_IPC_SAP class passing it the ACE_NONBLOCK flag
141
// Remove acceptor handle from further consideration. active_handles_.clr_bit (acceptor ().get_handle ());
} return 0; }
If the acceptor() handle is active, iteratively accept all the connections & save them in master_handle_set_
142
The figure also shows how we use the ACE::select() wrapper method & the ACE_Handle_Set class to service multiple clients via a reactive server model
146
class Reactive_Logging_Server_Ex : public Logging_Server { protected: // Associate an active handle to an <ACE_FILE_IO> pointer. LOG_MAP log_map_;
// Keep track of acceptor socket and all the connected // stream socket handles. ACE_Handle_Set master_handle_set_; // Keep track of read handles marked as active by <select>. ACE_Handle_Set active_read_handles_; typedef Logging_Server PARENT; // Other methods shown below... };
148
152
153
e.g., the UNIX fork() system function is very different than Windows CreateProcess() Addressing these platform variations in each application is difficult, tedious, error prone, & unnecessary since ACE provides the ACE_Process class
154
Windows
Using ACE_Process
This program uses ACE_Process to computes factorials recursively
int main (int argc, char *argv[]) { ACE_Process_Options options; char *n_env = 0; int n; if (argc == 1) { // Top-level process. n_env = ACE_OS::getenv ("FACTORIAL"); n = n_env == 0 ? 0 : atoi (n_env); options.command_line ("%s %d", argv[0], n == 0 ? 10 : n); } else if (atoi (argv[1]) == 1) return 1; // Base case. else { n = atoi (argv[1]); options.command_line ("%s %d", argv[0], n - 1); } Pass command line for spawned child to use ACE_Process child; child.spawn (options); // Make the ``recursive'' call. child.wait (); return n * child.exit_code (); // Compute n factorial. }
157
158
162
163
164
Master/Worker Process Creation Sequence for Windows Child doesnt automatically inherit the parent processs image, so we must take other steps e.g., pass the socket handle
166
Windows
return 0;
} Method only gets invoked for Win32
int run_worker (int argc, char *argv[]) { ACE_HANDLE socket_handle = ACE_static_cast (ACE_HANDLE, atoi (argv[2])); ACE_SOCK_Stream logging_peer (socket_handle); handle_data (&logging_peer); logging_peer.close (); return 0;
}
167
dynamic_cast<TYPE> (EXPR)
reinterpret_cast<TYPE> (EXPR)
These methods are largely deprecated in ACE since new C++ compilers are better
168
169
Singleton Pattern
Intent Ensure a class has only one instance & provide a global point of access to it Context Need to address initialization versus usage ordering Problem Want to ensure a single instance of a class, shared by all uses throughout a program
class Singleton { public: static Singleton *instance (){ if (instance_ == 0) { instance_ = new Singleton; } return instance_; } void method_1 (); // Other methods omitted private: // Private constructor Singleton (); // Initialized to 0 by linker. static Singleton *instance_; };
Solution Provide a global access method (static in C++) First use of the access method instantiates the class Constructors for instance are made private
170
virtual int handle_data (ACE_SOCK_Stream *logging_peer) { // Ensure blocking <recv>s. logging_peer->disable (ACE_NONBLOCK); ACE_FILE_IO log_file; make_log_file (log_file, logging_peer); Logging_Handler logging_handler (log_file, *logging_peer); while (logging_handler.log_record () != -1) continue; log_file.close (); return 0; handle_data() method similar to before (uses blocking I/O) }
171
174
ACE_Thread_Manager::spawn() Options
The ACE_Thread_Manager::spawn() method can be passed a set of flags to specify the properties of the created thread, whose value is a bitwise inclusive ``or'' of the flags shown in the following table:
177
178
Problem
Programmers forget to make the following distinction A thread is a unit of execution An object is a chunk of memory with associated methods There is no implicit connection between a thread & any object that the thread accesses during execution Forces
A thread should not be able access an object after the latter has been deleted
Solution Dynamically allocate the object passing it to the thread function as parameter & let the latter delete it
179
Regardless of OS, the following steps occur to spawn a thread: The OS creates a thread execution context The OS allocates memory for the thread's stack The new thread's register set is prepared so that when it's scheduled into execution, it will call the thread entry point function supplied as a parameter to spawn() The thread is marked runnable so the OS can start executing it
180
This method will also clean up resources, such as closing the peer connection
void *Thread_Per_Connection_Logging_Server::run_svc (void *arg) { auto_ptr<Thread_Args> thread_args (ACE_static_cast (Thread_Args *, arg)); Were responsible for deleting this dynamically allocated memory when were done, so we use an auto_ptr thread_args->this_->handle_data (&thread_args->logging_peer_); thread_args->logging_peer_.close (); return 0; // Return value is ignored. }
183
// Keep handling log records until client closes connection // or this thread is asked to cancel itself. while (!tm->testcancel (me) && logging_handler.log_record () != -1) continue; log_file.close (); return 0;
}
184
185
187
A consistent representation of scheduling priorities, which is necessary since higher scheduling priorities are indicated by lower priority values on some OS platforms 188
}
virtual int handle_data (ACE_SOCK_Stream *logging_client) { int prio = ACE_Sched_Params::next_priority (ACE_SCHED_FIFO, ACE_Sched_Params::priority_min (ACE_SCHED_FIFO), ACE_SCOPE_THREAD); Data handling is done at a higher priority than connections ACE_OS::thr_setprio (prio); return Thread_Per_Connection_Logging_Server::handle_data (logging_client); } };
190
191
For platforms that lack adequate TSS support natively (such as VxWorks) ACE_TSS emulates TSS efficiently
192
thread m
accesses key n
[k,t]
Thread-Specific Object
: Application Thread
: Key Factory
create_key()
key
: Thread-Specific Object
TSObject key set()
193
template <class TYPE> TYPE * ACE_TSS<TYPE>::operator-> () { if (once_ == 0) { // Ensure that we're serialized. ACE_GUARD_RETURN (ACE_Thread_Mutex, guard, keylock_, 0);
if (once_ == 0) { ACE_OS::thr_keycreate (&key_, &ACE_TSS<TYPE>::cleanup); once_ = 1; } } We used the double-checked locking optimization pattern here
194
// Initialize <ts_obj> from thread-specific storage. ACE_OS::thr_getspecific (key_, (void **) &ts_obj);
// Check if this method's been called in this thread. if (ts_obj == 0) { // Allocate memory off the heap and store it in a pointer. ts_obj = new TYPE; // Store the dynamically allocated pointer in TSS. ACE_OS::thr_setspecific (key_, ts_obj); } return ts_obj; }
195
}
196
197
198
Class Capabilities These classes implement the Scoped Locking idiom, which leverages the semantics of C++ class constructors & destructors to ensure a lock is acquired & released automatically upon entry to and exit from a block of C++ code, respectively
199
The Scoped Locking idiom defines a guard class whose constructor automatically acquires a lock when control enters a scope & whose destructor automatically releases the lock when control leaves the scope
void method () { ACE_Guard <ACE_Thread_Mutex> guard (lock_); // The lock is released when the method returns }
200
ACE_Write_Guard & ACE_Read_Guard acquire write locks & read locks, respectively
Instances of the ACE_Guard* classes can be allocated on the run-time stack to acquire & release locks in method or block scopes that define critical sections
201
203
204
205
// ... while (logging_handler_.log_record () != -1) { // Acquire lock in constructor. ACE_GUARD_RETURN (ACE_Recursive_Thread_Mutex, guard, ACE_Static_Object_Lock::instance (), -1); ++request_count; // Count # of requests // Release lock in destructor. } // ... ACE_Static_Object_Lock gets preinitialized before main() runs
206
Moreover, file-locking mechanisms work only at the system-scope level, rather than at process scope
207
// Atomically pre-increment <count_>. long operator++ () { // Use the <acquire_write> method to acquire a write lock. ACE_WRITE_GUARD_RETURN (ACE_RW_Thread_Mutex, guard, lock_, -1); return ++count_; }
209
210
Semaphores are a powerful mechanism used to lock and/or synchronize access to shared resources in concurrent applications
A semaphore contains a count that indicates the status of a shared resource Application designers assign the meaning of the semaphore's count, as well as its initial value Semaphores can therefore be used to mediate access to a pool of resources
211
The ACE_Thread_Semaphore & ACE_Process_Semaphore classes portably encapsulate process-scoped & system-scoped semaphores, respectively, in accordance with the Wrapper Faade pattern
These class interfaces are largely the same as the ACE_LOCK* pseudo-class The ACE_Null_Semaphore class implements all of its methods as ``no-op'' inline functions
212
private: // Implementations that enqueue/dequeue ACE_Message_Blocks. int enqueue_tail_i (ACE_Message_Block *, ACE_Time_Value * = 0); int dequeue_head_i (ACE_Message_Block *&, ACE_Time_Value * = 0);
// Implement the checks for boundary conditions. int is_empty_i () const; int is_full_i () const; // Lowest number before unblocking occurs. int low_water_mark_; // Greatest number of bytes before blocking. int high_water_mark_;
213
// C++ wrapper facades to coordinate concurrent access. mutable ACE_Thread_Mutex lock_; ACE_Thread_Semaphore notempty_; ACE_Thread_Semaphore notfull_; Using ACE_Thread_Semaphore // Remaining details of queue implementation omitted....
};
215
217
218
// Wait until the queue is no longer empty. while (is_empty_i () && result != -1) { ++dequeue_waiters_; guard.release (); result = notempty_.acquire (timeout); guard.acquire (); }
if (result == -1) { if (dequeue_waiters_ > 0) --dequeue_waiters_; if (errno == ETIME) errno = EWOULDBLOCK; return -1; }
219
// Remove the first message from the queue. int queued_messages = dequeue_head_i (first_item);
// Only signal if we've fallen below the low water mark. if (cur_bytes_ <= low_water_mark_ && enqueue_waiters_ > 0) { enqueue_waiters_--; notfull_.release (); } return queued_messages; // <guard> destructor releases <lock_> }
220
put() get()
<<get>>
Consumer Thread
uses 2
uses
ACE_Thread_Mutex
acquire() release()
222
223
int ACE_Recursive_Thread_Mutex::release () { // Automatically acquire mutex. ACE_GUARD_RETURN (ACE_Thread_Mutex, guard, lock_, -1); nesting_level_--; if (nesting_level_ == 0) { lock_available_.signal (); // Inform waiters the lock is free. owner_id_ = 0; } return 0; // Destructor of <guard> releases the <lock_>. }
224
Additional Information
Patterns & frameworks for concurrent & networked objects
www.posa.uci.edu ACE & TAO open-source middleware www.cs.wustl.edu/~schmidt/ACE.html www.cs.wustl.edu/~schmidt/TAO.html
ACE research papers www.cs.wustl.edu/~schmidt/ACE-papers.html Extended ACE & TAO tutorials UCLA extension, July 2004 www.cs.wustl.edu/~schmidt/UCLA.html ACE books www.cs.wustl.edu/~schmidt/ACE/ 226