2005-05-24 02:06:50 +04:00
|
|
|
/*
|
2005-11-05 22:57:48 +03:00
|
|
|
* Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
|
|
|
|
* University Research and Technology
|
|
|
|
* Corporation. All rights reserved.
|
2009-04-10 20:36:20 +04:00
|
|
|
* Copyright (c) 2004-2009 The University of Tennessee and The University
|
2005-11-05 22:57:48 +03:00
|
|
|
* of Tennessee Research Foundation. All rights
|
|
|
|
* reserved.
|
2007-08-23 11:09:43 +04:00
|
|
|
* Copyright (c) 2004-2007 High Performance Computing Center Stuttgart,
|
2005-05-24 02:06:50 +04:00
|
|
|
* University of Stuttgart. All rights reserved.
|
|
|
|
* Copyright (c) 2004-2005 The Regents of the University of California.
|
|
|
|
* All rights reserved.
|
2008-05-30 05:29:09 +04:00
|
|
|
* Copyright (c) 2008 UT-Battelle, LLC. All rights reserved.
|
2008-11-05 00:58:06 +03:00
|
|
|
* Copyright (c) 2006-2008 University of Houston. All rights reserved.
|
2005-05-24 02:06:50 +04:00
|
|
|
* $COPYRIGHT$
|
|
|
|
*
|
|
|
|
* Additional copyrights may follow
|
|
|
|
*
|
|
|
|
* $HEADER$
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "ompi_config.h"
|
|
|
|
|
2005-07-03 20:22:16 +04:00
|
|
|
#include "opal/class/opal_list.h"
|
2005-07-04 02:45:48 +04:00
|
|
|
#include "opal/threads/mutex.h"
|
2007-07-01 20:19:53 +04:00
|
|
|
#include "opal/prefetch.h"
|
2006-02-12 04:33:29 +03:00
|
|
|
#include "ompi/constants.h"
|
|
|
|
#include "ompi/communicator/communicator.h"
|
|
|
|
#include "ompi/mca/pml/pml.h"
|
2005-06-02 01:09:43 +04:00
|
|
|
#include "pml_ob1.h"
|
|
|
|
#include "pml_ob1_comm.h"
|
2005-05-24 02:06:50 +04:00
|
|
|
#include "pml_ob1_recvfrag.h"
|
|
|
|
#include "pml_ob1_recvreq.h"
|
2005-06-01 18:34:22 +04:00
|
|
|
#include "pml_ob1_sendreq.h"
|
2005-06-02 01:09:43 +04:00
|
|
|
#include "pml_ob1_hdr.h"
|
2006-03-31 21:09:09 +04:00
|
|
|
#include "ompi/peruse/peruse-internal.h"
|
2008-05-30 05:29:09 +04:00
|
|
|
#include "ompi/memchecker.h"
|
|
|
|
|
2005-05-24 02:06:50 +04:00
|
|
|
|
2007-07-11 03:45:23 +04:00
|
|
|
OBJ_CLASS_INSTANCE( mca_pml_ob1_buffer_t,
|
|
|
|
ompi_free_list_item_t,
|
|
|
|
NULL,
|
|
|
|
NULL );
|
|
|
|
|
|
|
|
OBJ_CLASS_INSTANCE( mca_pml_ob1_recv_frag_t,
|
|
|
|
opal_list_item_t,
|
|
|
|
NULL,
|
|
|
|
NULL );
|
2005-05-24 02:06:50 +04:00
|
|
|
|
2006-07-13 20:52:40 +04:00
|
|
|
/**
|
|
|
|
* Static functions.
|
|
|
|
*/
|
2005-05-24 02:06:50 +04:00
|
|
|
|
2008-09-16 03:04:18 +04:00
|
|
|
/**
|
|
|
|
* Append a unexpected descriptor to a queue. This function will allocate and
|
2009-07-16 21:41:32 +04:00
|
|
|
* initialize the fragment (if necessary) and then will add it to the specified
|
|
|
|
* queue. The allocated fragment is not returned to the caller.
|
2008-09-16 03:04:18 +04:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
append_frag_to_list(opal_list_t *queue, mca_btl_base_module_t *btl,
|
|
|
|
mca_pml_ob1_match_hdr_t *hdr, mca_btl_base_segment_t* segments,
|
|
|
|
size_t num_segments, mca_pml_ob1_recv_frag_t* frag)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if(NULL == frag) {
|
|
|
|
MCA_PML_OB1_RECV_FRAG_ALLOC(frag, rc);
|
|
|
|
MCA_PML_OB1_RECV_FRAG_INIT(frag, hdr, segments, num_segments, btl);
|
|
|
|
}
|
|
|
|
opal_list_append(queue, (opal_list_item_t*)frag);
|
|
|
|
}
|
|
|
|
|
2006-07-13 20:52:40 +04:00
|
|
|
/**
|
|
|
|
* Match incoming recv_frags against posted receives.
|
|
|
|
* Supports out of order delivery.
|
|
|
|
*
|
|
|
|
* @param frag_header (IN) Header of received recv_frag.
|
|
|
|
* @param frag_desc (IN) Received recv_frag descriptor.
|
|
|
|
* @param match_made (OUT) Flag indicating wether a match was made.
|
|
|
|
* @param additional_matches (OUT) List of additional matches
|
|
|
|
* @return OMPI_SUCCESS or error status on failure.
|
|
|
|
*/
|
|
|
|
static int mca_pml_ob1_recv_frag_match( mca_btl_base_module_t *btl,
|
|
|
|
mca_pml_ob1_match_hdr_t *hdr,
|
|
|
|
mca_btl_base_segment_t* segments,
|
2008-05-30 05:29:09 +04:00
|
|
|
size_t num_segments,
|
|
|
|
int type);
|
|
|
|
|
2009-07-16 21:41:32 +04:00
|
|
|
static mca_pml_ob1_recv_request_t*
|
|
|
|
match_one(mca_btl_base_module_t *btl,
|
|
|
|
mca_pml_ob1_match_hdr_t *hdr, mca_btl_base_segment_t* segments,
|
|
|
|
size_t num_segments, ompi_communicator_t *comm_ptr,
|
|
|
|
mca_pml_ob1_comm_proc_t *proc,
|
|
|
|
mca_pml_ob1_recv_frag_t* frag);
|
2008-05-30 05:29:09 +04:00
|
|
|
|
|
|
|
void mca_pml_ob1_recv_frag_callback_match(mca_btl_base_module_t* btl,
|
|
|
|
mca_btl_base_tag_t tag,
|
|
|
|
mca_btl_base_descriptor_t* des,
|
2009-08-05 07:12:33 +04:00
|
|
|
void* cbdata )
|
|
|
|
{
|
2008-09-16 02:56:23 +04:00
|
|
|
mca_btl_base_segment_t* segments = des->des_dst;
|
|
|
|
mca_pml_ob1_match_hdr_t* hdr = (mca_pml_ob1_match_hdr_t*)segments->seg_addr.pval;
|
2008-05-30 05:29:09 +04:00
|
|
|
ompi_communicator_t *comm_ptr;
|
|
|
|
mca_pml_ob1_recv_request_t *match = NULL;
|
|
|
|
mca_pml_ob1_comm_t *comm;
|
|
|
|
mca_pml_ob1_comm_proc_t *proc;
|
|
|
|
size_t num_segments = des->des_dst_cnt;
|
|
|
|
size_t bytes_received = 0;
|
|
|
|
|
2008-09-16 02:56:23 +04:00
|
|
|
if( OPAL_UNLIKELY(segments->seg_len < OMPI_PML_OB1_MATCH_HDR_LEN) ) {
|
2008-05-30 05:29:09 +04:00
|
|
|
return;
|
2008-11-05 00:58:06 +03:00
|
|
|
}
|
2008-05-30 05:29:09 +04:00
|
|
|
ob1_hdr_ntoh(((mca_pml_ob1_hdr_t*) hdr), MCA_PML_OB1_HDR_TYPE_MATCH);
|
|
|
|
|
|
|
|
/* communicator pointer */
|
|
|
|
comm_ptr = ompi_comm_lookup(hdr->hdr_ctx);
|
|
|
|
if(OPAL_UNLIKELY(NULL == comm_ptr)) {
|
2008-07-17 08:50:39 +04:00
|
|
|
/* This is a special case. A message for a not yet existing
|
2008-09-16 03:04:18 +04:00
|
|
|
* communicator can happens. Instead of doing a matching we
|
|
|
|
* will temporarily add it the a pending queue in the PML.
|
|
|
|
* Later on, when the communicator is completely instantiated,
|
|
|
|
* this pending queue will be searched and all matching fragments
|
|
|
|
* moved to the right communicator.
|
2008-07-17 08:50:39 +04:00
|
|
|
*/
|
2008-09-16 03:04:18 +04:00
|
|
|
append_frag_to_list( &mca_pml_ob1.non_existing_communicator_pending,
|
2009-07-16 21:41:32 +04:00
|
|
|
btl, hdr, segments, num_segments, NULL );
|
2008-05-30 05:29:09 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
comm = (mca_pml_ob1_comm_t *)comm_ptr->c_pml_comm;
|
|
|
|
|
|
|
|
/* source sequence number */
|
|
|
|
proc = &comm->procs[hdr->hdr_src];
|
|
|
|
|
2008-07-17 08:50:39 +04:00
|
|
|
/* We generate the MSG_ARRIVED event as soon as the PML is aware
|
|
|
|
* of a matching fragment arrival. Independing if it is received
|
|
|
|
* on the correct order or not. This will allow the tools to
|
|
|
|
* figure out if the messages are not received in the correct
|
|
|
|
* order (if multiple network interfaces).
|
|
|
|
*/
|
2008-05-30 05:29:09 +04:00
|
|
|
PERUSE_TRACE_MSG_EVENT(PERUSE_COMM_MSG_ARRIVED, comm_ptr,
|
|
|
|
hdr->hdr_src, hdr->hdr_tag, PERUSE_RECV);
|
|
|
|
|
|
|
|
/* get next expected message sequence number - if threaded
|
2008-07-17 08:50:39 +04:00
|
|
|
* run, lock to make sure that if another thread is processing
|
|
|
|
* a frag from the same message a match is made only once.
|
|
|
|
* Also, this prevents other posted receives (for a pair of
|
|
|
|
* end points) from being processed, and potentially "loosing"
|
|
|
|
* the fragment.
|
|
|
|
*/
|
2008-05-30 05:29:09 +04:00
|
|
|
OPAL_THREAD_LOCK(&comm->matching_lock);
|
|
|
|
|
|
|
|
/* get sequence number of next message that can be processed */
|
|
|
|
if(OPAL_UNLIKELY((((uint16_t) hdr->hdr_seq) != ((uint16_t) proc->expected_sequence)) ||
|
2008-08-06 17:38:21 +04:00
|
|
|
(opal_list_get_size(&proc->frags_cant_match) > 0 ))) {
|
2008-07-17 08:50:39 +04:00
|
|
|
goto slow_path;
|
2008-05-30 05:29:09 +04:00
|
|
|
}
|
|
|
|
|
2008-07-17 08:50:39 +04:00
|
|
|
/* This is the sequence number we were expecting, so we can try
|
|
|
|
* matching it to already posted receives.
|
|
|
|
*/
|
2008-05-30 05:29:09 +04:00
|
|
|
|
|
|
|
/* We're now expecting the next sequence number. */
|
|
|
|
proc->expected_sequence++;
|
2008-07-17 08:50:39 +04:00
|
|
|
|
|
|
|
/* We generate the SEARCH_POSTED_QUEUE only when the message is
|
|
|
|
* received in the correct sequence. Otherwise, we delay the event
|
|
|
|
* generation until we reach the correct sequence number.
|
|
|
|
*/
|
2008-05-30 05:29:09 +04:00
|
|
|
PERUSE_TRACE_MSG_EVENT(PERUSE_COMM_SEARCH_POSTED_Q_BEGIN, comm_ptr,
|
|
|
|
hdr->hdr_src, hdr->hdr_tag, PERUSE_RECV);
|
|
|
|
|
2009-07-16 21:41:32 +04:00
|
|
|
match = match_one(btl, hdr, segments, num_segments, comm_ptr, proc, NULL);
|
2008-05-30 05:29:09 +04:00
|
|
|
|
2008-07-17 08:50:39 +04:00
|
|
|
/* The match is over. We generate the SEARCH_POSTED_Q_END here,
|
|
|
|
* before going into the mca_pml_ob1_check_cantmatch_for_match so
|
|
|
|
* we can make a difference for the searching time for all
|
|
|
|
* messages.
|
|
|
|
*/
|
2008-05-30 05:29:09 +04:00
|
|
|
PERUSE_TRACE_MSG_EVENT(PERUSE_COMM_SEARCH_POSTED_Q_END, comm_ptr,
|
|
|
|
hdr->hdr_src, hdr->hdr_tag, PERUSE_RECV);
|
|
|
|
|
|
|
|
/* release matching lock before processing fragment */
|
|
|
|
OPAL_THREAD_UNLOCK(&comm->matching_lock);
|
|
|
|
|
|
|
|
if(OPAL_LIKELY(match)) {
|
2008-09-16 02:56:23 +04:00
|
|
|
bytes_received = segments->seg_len - OMPI_PML_OB1_MATCH_HDR_LEN;
|
2008-05-30 05:29:09 +04:00
|
|
|
match->req_recv.req_bytes_packed = bytes_received;
|
|
|
|
|
|
|
|
MCA_PML_OB1_RECV_REQUEST_MATCHED(match, hdr);
|
2009-04-10 20:36:20 +04:00
|
|
|
if(match->req_bytes_expected > 0) {
|
2008-05-30 05:29:09 +04:00
|
|
|
struct iovec iov[2];
|
2008-08-06 17:38:21 +04:00
|
|
|
uint32_t iov_count = 1;
|
2008-05-30 05:29:09 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Make user buffer accessable(defined) before unpacking.
|
|
|
|
*/
|
|
|
|
MEMCHECKER(
|
|
|
|
memchecker_call(&opal_memchecker_base_mem_defined,
|
|
|
|
match->req_recv.req_base.req_addr,
|
|
|
|
match->req_recv.req_base.req_count,
|
|
|
|
match->req_recv.req_base.req_datatype);
|
|
|
|
);
|
|
|
|
|
|
|
|
iov[0].iov_len = bytes_received;
|
2008-09-16 02:56:23 +04:00
|
|
|
iov[0].iov_base = (IOVBASE_TYPE*)((unsigned char*)segments->seg_addr.pval +
|
2008-05-30 07:58:39 +04:00
|
|
|
OMPI_PML_OB1_MATCH_HDR_LEN);
|
2008-08-06 17:38:21 +04:00
|
|
|
while (iov_count < num_segments) {
|
2008-09-16 02:56:23 +04:00
|
|
|
bytes_received += segments[iov_count].seg_len;
|
|
|
|
iov[iov_count].iov_len = segments[iov_count].seg_len;
|
|
|
|
iov[iov_count].iov_base = (IOVBASE_TYPE*)((unsigned char*)segments[iov_count].seg_addr.pval);
|
2008-08-06 17:38:21 +04:00
|
|
|
iov_count++;
|
2008-05-30 05:29:09 +04:00
|
|
|
}
|
- Split the datatype engine into two parts: an MPI specific part in
OMPI
and a language agnostic part in OPAL. The convertor is completely
moved into OPAL. This offers several benefits as described in RFC
http://www.open-mpi.org/community/lists/devel/2009/07/6387.php
namely:
- Fewer basic types (int* and float* types, boolean and wchar
- Fixing naming scheme to ompi-nomenclature.
- Usability outside of the ompi-layer.
- Due to the fixed nature of simple opal types, their information is
completely
known at compile time and therefore constified
- With fewer datatypes (22), the actual sizes of bit-field types may be
reduced
from 64 to 32 bits, allowing reorganizing the opal_datatype
structure, eliminating holes and keeping data required in convertor
(upon send/recv) in one cacheline...
This has implications to the convertor-datastructure and other parts
of the code.
- Several performance tests have been run, the netpipe latency does not
change with
this patch on Linux/x86-64 on the smoky cluster.
- Extensive tests have been done to verify correctness (no new
regressions) using:
1. mpi_test_suite on linux/x86-64 using clean ompi-trunk and
ompi-ddt:
a. running both trunk and ompi-ddt resulted in no differences
(except for MPI_SHORT_INT and MPI_TYPE_MIX_LB_UB do now run
correctly).
b. with --enable-memchecker and running under valgrind (one buglet
when run with static found in test-suite, commited)
2. ibm testsuite on linux/x86-64 using clean ompi-trunk and ompi-ddt:
all passed (except for the dynamic/ tests failed!! as trunk/MTT)
3. compilation and usage of HDF5 tests on Jaguar using PGI and
PathScale compilers.
4. compilation and usage on Scicortex.
- Please note, that for the heterogeneous case, (-m32 compiled
binaries/ompi), neither
ompi-trunk, nor ompi-ddt branch would successfully launch.
This commit was SVN r21641.
2009-07-13 08:56:31 +04:00
|
|
|
opal_convertor_unpack( &match->req_recv.req_base.req_convertor,
|
2008-05-30 05:29:09 +04:00
|
|
|
iov,
|
|
|
|
&iov_count,
|
|
|
|
&bytes_received );
|
|
|
|
match->req_bytes_received = bytes_received;
|
|
|
|
/*
|
|
|
|
* Unpacking finished, make the user buffer unaccessable again.
|
|
|
|
*/
|
|
|
|
MEMCHECKER(
|
|
|
|
memchecker_call(&opal_memchecker_base_mem_noaccess,
|
|
|
|
match->req_recv.req_base.req_addr,
|
|
|
|
match->req_recv.req_base.req_count,
|
|
|
|
match->req_recv.req_base.req_datatype);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no need to check if complete we know we are.. */
|
|
|
|
/* don't need a rmb as that is for checking */
|
|
|
|
recv_request_pml_complete(match);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
slow_path:
|
|
|
|
OPAL_THREAD_UNLOCK(&comm->matching_lock);
|
2008-09-16 02:56:23 +04:00
|
|
|
mca_pml_ob1_recv_frag_match(btl, hdr, segments,
|
2008-05-30 05:29:09 +04:00
|
|
|
num_segments, MCA_PML_OB1_HDR_TYPE_MATCH);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void mca_pml_ob1_recv_frag_callback_rndv(mca_btl_base_module_t* btl,
|
|
|
|
mca_btl_base_tag_t tag,
|
|
|
|
mca_btl_base_descriptor_t* des,
|
2009-08-05 07:12:33 +04:00
|
|
|
void* cbdata )
|
|
|
|
{
|
2008-05-30 05:29:09 +04:00
|
|
|
mca_btl_base_segment_t* segments = des->des_dst;
|
|
|
|
mca_pml_ob1_hdr_t* hdr = (mca_pml_ob1_hdr_t*)segments->seg_addr.pval;
|
|
|
|
|
|
|
|
if( OPAL_UNLIKELY(segments->seg_len < sizeof(mca_pml_ob1_common_hdr_t)) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ob1_hdr_ntoh(hdr, MCA_PML_OB1_HDR_TYPE_RNDV);
|
|
|
|
mca_pml_ob1_recv_frag_match(btl, &hdr->hdr_match, segments,
|
|
|
|
des->des_dst_cnt, MCA_PML_OB1_HDR_TYPE_RNDV);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mca_pml_ob1_recv_frag_callback_rget(mca_btl_base_module_t* btl,
|
|
|
|
mca_btl_base_tag_t tag,
|
|
|
|
mca_btl_base_descriptor_t* des,
|
2009-08-05 07:12:33 +04:00
|
|
|
void* cbdata )
|
|
|
|
{
|
2008-05-30 05:29:09 +04:00
|
|
|
mca_btl_base_segment_t* segments = des->des_dst;
|
|
|
|
mca_pml_ob1_hdr_t* hdr = (mca_pml_ob1_hdr_t*)segments->seg_addr.pval;
|
|
|
|
|
|
|
|
if( OPAL_UNLIKELY(segments->seg_len < sizeof(mca_pml_ob1_common_hdr_t)) ) {
|
|
|
|
return;
|
2009-08-05 07:12:33 +04:00
|
|
|
}
|
2008-05-30 05:29:09 +04:00
|
|
|
ob1_hdr_ntoh(hdr, MCA_PML_OB1_HDR_TYPE_RGET);
|
|
|
|
mca_pml_ob1_recv_frag_match(btl, &hdr->hdr_match, segments,
|
|
|
|
des->des_dst_cnt, MCA_PML_OB1_HDR_TYPE_RGET);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void mca_pml_ob1_recv_frag_callback_ack(mca_btl_base_module_t* btl,
|
|
|
|
mca_btl_base_tag_t tag,
|
|
|
|
mca_btl_base_descriptor_t* des,
|
2009-08-05 07:12:33 +04:00
|
|
|
void* cbdata )
|
|
|
|
{
|
2005-06-30 09:50:55 +04:00
|
|
|
mca_btl_base_segment_t* segments = des->des_dst;
|
2007-01-05 01:07:37 +03:00
|
|
|
mca_pml_ob1_hdr_t* hdr = (mca_pml_ob1_hdr_t*)segments->seg_addr.pval;
|
2008-05-30 05:29:09 +04:00
|
|
|
mca_pml_ob1_send_request_t* sendreq;
|
|
|
|
|
|
|
|
if( OPAL_UNLIKELY(segments->seg_len < sizeof(mca_pml_ob1_common_hdr_t)) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ob1_hdr_ntoh(hdr, MCA_PML_OB1_HDR_TYPE_ACK);
|
|
|
|
sendreq = (mca_pml_ob1_send_request_t*)hdr->hdr_ack.hdr_src_req.pval;
|
|
|
|
sendreq->req_recv = hdr->hdr_ack.hdr_dst_req;
|
|
|
|
|
|
|
|
/* if the request should be delivered entirely by copy in/out
|
|
|
|
* then throttle sends */
|
|
|
|
if(hdr->hdr_common.hdr_flags & MCA_PML_OB1_HDR_FLAGS_NORDMA)
|
|
|
|
sendreq->req_throttle_sends = true;
|
|
|
|
|
|
|
|
mca_pml_ob1_send_request_copy_in_out(sendreq,
|
|
|
|
hdr->hdr_ack.hdr_send_offset,
|
|
|
|
sendreq->req_send.req_bytes_packed -
|
|
|
|
hdr->hdr_ack.hdr_send_offset);
|
|
|
|
|
|
|
|
OPAL_THREAD_ADD32(&sendreq->req_state, -1);
|
|
|
|
|
|
|
|
if(send_request_pml_complete_check(sendreq) == false)
|
|
|
|
mca_pml_ob1_send_request_schedule(sendreq);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mca_pml_ob1_recv_frag_callback_frag(mca_btl_base_module_t* btl,
|
|
|
|
mca_btl_base_tag_t tag,
|
|
|
|
mca_btl_base_descriptor_t* des,
|
|
|
|
void* cbdata ) {
|
|
|
|
mca_btl_base_segment_t* segments = des->des_dst;
|
|
|
|
mca_pml_ob1_hdr_t* hdr = (mca_pml_ob1_hdr_t*)segments->seg_addr.pval;
|
|
|
|
mca_pml_ob1_recv_request_t* recvreq;
|
|
|
|
|
|
|
|
if( OPAL_UNLIKELY(segments->seg_len < sizeof(mca_pml_ob1_common_hdr_t)) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ob1_hdr_ntoh(hdr, MCA_PML_OB1_HDR_TYPE_FRAG);
|
|
|
|
recvreq = (mca_pml_ob1_recv_request_t*)hdr->hdr_frag.hdr_dst_req.pval;
|
|
|
|
mca_pml_ob1_recv_request_progress_frag(recvreq,btl,segments,des->des_dst_cnt);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-07-11 03:45:23 +04:00
|
|
|
|
2008-05-30 05:29:09 +04:00
|
|
|
void mca_pml_ob1_recv_frag_callback_put(mca_btl_base_module_t* btl,
|
|
|
|
mca_btl_base_tag_t tag,
|
|
|
|
mca_btl_base_descriptor_t* des,
|
|
|
|
void* cbdata ) {
|
|
|
|
mca_btl_base_segment_t* segments = des->des_dst;
|
|
|
|
mca_pml_ob1_hdr_t* hdr = (mca_pml_ob1_hdr_t*)segments->seg_addr.pval;
|
|
|
|
mca_pml_ob1_send_request_t* sendreq;
|
|
|
|
|
2007-07-11 03:45:23 +04:00
|
|
|
if( OPAL_UNLIKELY(segments->seg_len < sizeof(mca_pml_ob1_common_hdr_t)) ) {
|
2005-05-24 02:06:50 +04:00
|
|
|
return;
|
|
|
|
}
|
2008-05-30 05:29:09 +04:00
|
|
|
|
|
|
|
ob1_hdr_ntoh(hdr, MCA_PML_OB1_HDR_TYPE_PUT);
|
|
|
|
sendreq = (mca_pml_ob1_send_request_t*)hdr->hdr_rdma.hdr_req.pval;
|
|
|
|
mca_pml_ob1_send_request_put(sendreq,btl,&hdr->hdr_rdma);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2006-03-16 01:53:41 +03:00
|
|
|
|
2008-05-30 05:29:09 +04:00
|
|
|
|
|
|
|
void mca_pml_ob1_recv_frag_callback_fin(mca_btl_base_module_t* btl,
|
|
|
|
mca_btl_base_tag_t tag,
|
|
|
|
mca_btl_base_descriptor_t* des,
|
|
|
|
void* cbdata ) {
|
|
|
|
mca_btl_base_segment_t* segments = des->des_dst;
|
|
|
|
mca_pml_ob1_hdr_t* hdr = (mca_pml_ob1_hdr_t*)segments->seg_addr.pval;
|
|
|
|
mca_btl_base_descriptor_t* rdma;
|
|
|
|
|
|
|
|
if( OPAL_UNLIKELY(segments->seg_len < sizeof(mca_pml_ob1_common_hdr_t)) ) {
|
|
|
|
return;
|
2005-05-24 02:06:50 +04:00
|
|
|
}
|
2008-05-30 05:29:09 +04:00
|
|
|
|
|
|
|
ob1_hdr_ntoh(hdr, MCA_PML_OB1_HDR_TYPE_FIN);
|
|
|
|
rdma = (mca_btl_base_descriptor_t*)hdr->hdr_fin.hdr_des.pval;
|
|
|
|
rdma->des_cbfunc(btl, NULL, rdma,
|
|
|
|
hdr->hdr_fin.hdr_fail ? OMPI_ERROR : OMPI_SUCCESS);
|
|
|
|
|
|
|
|
return;
|
2005-05-24 02:06:50 +04:00
|
|
|
}
|
2006-02-09 09:59:54 +03:00
|
|
|
|
2008-05-30 05:29:09 +04:00
|
|
|
|
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
#define PML_MAX_SEQ ~((mca_pml_sequence_t)0);
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
static inline mca_pml_ob1_recv_request_t* get_posted_recv(opal_list_t *queue)
|
|
|
|
{
|
|
|
|
if(opal_list_get_size(queue) == 0)
|
|
|
|
return NULL;
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
return (mca_pml_ob1_recv_request_t*)opal_list_get_first(queue);
|
|
|
|
}
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
static inline mca_pml_ob1_recv_request_t* get_next_posted_recv(
|
|
|
|
opal_list_t *queue,
|
|
|
|
mca_pml_ob1_recv_request_t* req)
|
|
|
|
{
|
|
|
|
opal_list_item_t *i = opal_list_get_next((opal_list_item_t*)req);
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
if(opal_list_get_end(queue) == i)
|
|
|
|
return NULL;
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
return (mca_pml_ob1_recv_request_t*)i;
|
|
|
|
}
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
static mca_pml_ob1_recv_request_t *match_incomming(
|
|
|
|
mca_pml_ob1_match_hdr_t *hdr, mca_pml_ob1_comm_t *comm,
|
|
|
|
mca_pml_ob1_comm_proc_t *proc)
|
|
|
|
{
|
|
|
|
mca_pml_ob1_recv_request_t *specific_recv, *wild_recv;
|
|
|
|
mca_pml_sequence_t wild_recv_seq, specific_recv_seq;
|
|
|
|
int tag = hdr->hdr_tag;
|
|
|
|
|
|
|
|
specific_recv = get_posted_recv(&proc->specific_receives);
|
|
|
|
wild_recv = get_posted_recv(&comm->wild_receives);
|
|
|
|
|
|
|
|
wild_recv_seq = wild_recv ?
|
|
|
|
wild_recv->req_recv.req_base.req_sequence : PML_MAX_SEQ;
|
|
|
|
specific_recv_seq = specific_recv ?
|
|
|
|
specific_recv->req_recv.req_base.req_sequence : PML_MAX_SEQ;
|
|
|
|
|
|
|
|
/* they are equal only if both are PML_MAX_SEQ */
|
|
|
|
while(wild_recv_seq != specific_recv_seq) {
|
|
|
|
mca_pml_ob1_recv_request_t **match;
|
|
|
|
opal_list_t *queue;
|
|
|
|
int req_tag;
|
|
|
|
mca_pml_sequence_t *seq;
|
|
|
|
|
|
|
|
if (OPAL_UNLIKELY(wild_recv_seq < specific_recv_seq)) {
|
|
|
|
match = &wild_recv;
|
|
|
|
queue = &comm->wild_receives;
|
|
|
|
seq = &wild_recv_seq;
|
|
|
|
} else {
|
|
|
|
match = &specific_recv;
|
|
|
|
queue = &proc->specific_receives;
|
|
|
|
seq = &specific_recv_seq;
|
|
|
|
}
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
req_tag = (*match)->req_recv.req_base.req_tag;
|
|
|
|
if(req_tag == tag || (req_tag == OMPI_ANY_TAG && tag >= 0)) {
|
|
|
|
opal_list_remove_item(queue, (opal_list_item_t*)(*match));
|
|
|
|
PERUSE_TRACE_COMM_EVENT(PERUSE_COMM_REQ_REMOVE_FROM_POSTED_Q,
|
|
|
|
&((*match)->req_recv.req_base), PERUSE_RECV);
|
|
|
|
return *match;
|
|
|
|
}
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
*match = get_next_posted_recv(queue, *match);
|
|
|
|
*seq = (*match) ? (*match)->req_recv.req_base.req_sequence : PML_MAX_SEQ;
|
|
|
|
}
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2009-07-16 21:41:32 +04:00
|
|
|
static mca_pml_ob1_recv_request_t*
|
|
|
|
match_one(mca_btl_base_module_t *btl,
|
|
|
|
mca_pml_ob1_match_hdr_t *hdr, mca_btl_base_segment_t* segments,
|
|
|
|
size_t num_segments, ompi_communicator_t *comm_ptr,
|
|
|
|
mca_pml_ob1_comm_proc_t *proc,
|
|
|
|
mca_pml_ob1_recv_frag_t* frag)
|
2007-12-19 12:16:20 +03:00
|
|
|
{
|
|
|
|
mca_pml_ob1_recv_request_t *match;
|
|
|
|
mca_pml_ob1_comm_t *comm = (mca_pml_ob1_comm_t *)comm_ptr->c_pml_comm;
|
|
|
|
|
|
|
|
do {
|
|
|
|
match = match_incomming(hdr, comm, proc);
|
|
|
|
|
|
|
|
/* if match found, process data */
|
2009-07-16 21:41:32 +04:00
|
|
|
if(OPAL_LIKELY(NULL != match)) {
|
|
|
|
match->req_recv.req_base.req_proc = proc->ompi_proc;
|
|
|
|
|
|
|
|
if(OPAL_UNLIKELY(MCA_PML_REQUEST_PROBE == match->req_recv.req_base.req_type)) {
|
|
|
|
/* complete the probe */
|
|
|
|
mca_pml_ob1_recv_request_matched_probe(match, btl, segments,
|
|
|
|
num_segments);
|
|
|
|
/* attempt to match actual request */
|
|
|
|
continue;
|
|
|
|
}
|
2007-12-19 12:16:20 +03:00
|
|
|
|
2009-07-16 21:41:32 +04:00
|
|
|
PERUSE_TRACE_COMM_EVENT(PERUSE_COMM_MSG_MATCH_POSTED_REQ,
|
|
|
|
&(match->req_recv.req_base), PERUSE_RECV);
|
|
|
|
return match;
|
2007-12-19 12:16:20 +03:00
|
|
|
}
|
|
|
|
|
2009-07-16 21:41:32 +04:00
|
|
|
/* if no match found, place on unexpected queue */
|
|
|
|
append_frag_to_list(&proc->unexpected_frags, btl, hdr, segments,
|
|
|
|
num_segments, frag);
|
|
|
|
PERUSE_TRACE_MSG_EVENT(PERUSE_COMM_MSG_INSERT_IN_UNEX_Q, comm_ptr,
|
|
|
|
hdr->hdr_src, hdr->hdr_tag, PERUSE_RECV);
|
|
|
|
return NULL;
|
2007-12-19 12:16:20 +03:00
|
|
|
} while(true);
|
|
|
|
}
|
|
|
|
|
2009-08-05 07:12:33 +04:00
|
|
|
static mca_pml_ob1_recv_frag_t* check_cantmatch_for_match(mca_pml_ob1_comm_proc_t *proc)
|
2007-12-19 12:16:20 +03:00
|
|
|
{
|
|
|
|
mca_pml_ob1_recv_frag_t *frag;
|
|
|
|
|
2008-07-17 08:50:39 +04:00
|
|
|
/* search the list for a fragment from the send with sequence
|
|
|
|
* number next_msg_seq_expected
|
|
|
|
*/
|
2009-08-05 07:12:33 +04:00
|
|
|
for(frag = (mca_pml_ob1_recv_frag_t*)opal_list_get_first(&proc->frags_cant_match);
|
|
|
|
frag != (mca_pml_ob1_recv_frag_t*)opal_list_get_end(&proc->frags_cant_match);
|
|
|
|
frag = (mca_pml_ob1_recv_frag_t*)opal_list_get_next(frag))
|
2007-12-19 12:16:20 +03:00
|
|
|
{
|
|
|
|
mca_pml_ob1_match_hdr_t* hdr = &frag->hdr.hdr_match;
|
|
|
|
/*
|
|
|
|
* If the message has the next expected seq from that proc...
|
|
|
|
*/
|
2008-04-27 22:35:54 +04:00
|
|
|
if(hdr->hdr_seq != proc->expected_sequence)
|
2007-12-19 12:16:20 +03:00
|
|
|
continue;
|
|
|
|
|
|
|
|
opal_list_remove_item(&proc->frags_cant_match, (opal_list_item_t*)frag);
|
|
|
|
return frag;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2005-06-02 01:09:43 +04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* RCS/CTS receive side matching
|
|
|
|
*
|
|
|
|
* @param hdr list of parameters needed for matching
|
2005-06-07 18:12:47 +04:00
|
|
|
* This list is also embeded in frag,
|
2005-06-02 01:09:43 +04:00
|
|
|
* but this allows to save a memory copy when
|
|
|
|
* a match is made in this routine. (IN)
|
2005-06-07 18:12:47 +04:00
|
|
|
* @param frag pointer to receive fragment which we want
|
2005-06-02 01:09:43 +04:00
|
|
|
* to match (IN/OUT). If a match is not made,
|
2005-06-07 18:12:47 +04:00
|
|
|
* hdr is copied to frag.
|
|
|
|
* @param match_made parameter indicating if we matched frag/
|
2005-06-02 01:09:43 +04:00
|
|
|
* hdr (OUT)
|
2005-06-07 18:12:47 +04:00
|
|
|
* @param additional_matches if a match is made with frag, we
|
2005-06-02 01:09:43 +04:00
|
|
|
* may be able to match fragments that previously
|
|
|
|
* have arrived out-of-order. If this is the
|
|
|
|
* case, the associated fragment descriptors are
|
|
|
|
* put on this list for further processing. (OUT)
|
|
|
|
*
|
|
|
|
* @return OMPI error code
|
|
|
|
*
|
|
|
|
* This routine is used to try and match a newly arrived message fragment
|
|
|
|
* to pre-posted receives. The following assumptions are made
|
|
|
|
* - fragments are received out of order
|
|
|
|
* - for long messages, e.g. more than one fragment, a RTS/CTS algorithm
|
|
|
|
* is used.
|
|
|
|
* - 2nd and greater fragments include a receive descriptor pointer
|
|
|
|
* - fragments may be dropped
|
|
|
|
* - fragments may be corrupt
|
|
|
|
* - this routine may be called simultaneously by more than one thread
|
|
|
|
*/
|
2006-07-13 20:52:40 +04:00
|
|
|
static int mca_pml_ob1_recv_frag_match( mca_btl_base_module_t *btl,
|
|
|
|
mca_pml_ob1_match_hdr_t *hdr,
|
|
|
|
mca_btl_base_segment_t* segments,
|
2008-05-30 05:29:09 +04:00
|
|
|
size_t num_segments,
|
|
|
|
int type)
|
2005-06-02 01:09:43 +04:00
|
|
|
{
|
|
|
|
/* local variables */
|
|
|
|
uint16_t next_msg_seq_expected, frag_msg_seq;
|
|
|
|
ompi_communicator_t *comm_ptr;
|
|
|
|
mca_pml_ob1_recv_request_t *match = NULL;
|
|
|
|
mca_pml_ob1_comm_t *comm;
|
|
|
|
mca_pml_ob1_comm_proc_t *proc;
|
2007-12-19 12:16:20 +03:00
|
|
|
mca_pml_ob1_recv_frag_t* frag = NULL;
|
2005-06-02 01:09:43 +04:00
|
|
|
|
|
|
|
/* communicator pointer */
|
2007-09-12 21:52:02 +04:00
|
|
|
comm_ptr = ompi_comm_lookup(hdr->hdr_ctx);
|
2007-12-19 12:16:20 +03:00
|
|
|
if(OPAL_UNLIKELY(NULL == comm_ptr)) {
|
2008-09-16 03:04:18 +04:00
|
|
|
/* This is a special case. A message for a not yet existing
|
|
|
|
* communicator can happens. Instead of doing a matching we
|
|
|
|
* will temporarily add it the a pending queue in the PML.
|
|
|
|
* Later on, when the communicator is completely instantiated,
|
|
|
|
* this pending queue will be searched and all matching fragments
|
|
|
|
* moved to the right communicator.
|
2007-09-12 21:52:02 +04:00
|
|
|
*/
|
2008-09-16 03:04:18 +04:00
|
|
|
append_frag_to_list( &mca_pml_ob1.non_existing_communicator_pending,
|
2009-07-16 21:41:32 +04:00
|
|
|
btl, hdr, segments, num_segments, NULL );
|
2007-09-12 21:52:02 +04:00
|
|
|
return OMPI_SUCCESS;
|
|
|
|
}
|
|
|
|
comm = (mca_pml_ob1_comm_t *)comm_ptr->c_pml_comm;
|
2005-06-02 01:09:43 +04:00
|
|
|
|
|
|
|
/* source sequence number */
|
2005-08-17 22:23:38 +04:00
|
|
|
frag_msg_seq = hdr->hdr_seq;
|
2007-12-19 12:16:20 +03:00
|
|
|
proc = &comm->procs[hdr->hdr_src];
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2006-03-31 21:09:09 +04:00
|
|
|
/**
|
|
|
|
* We generate the MSG_ARRIVED event as soon as the PML is aware of a matching
|
|
|
|
* fragment arrival. Independing if it is received on the correct order or not.
|
|
|
|
* This will allow the tools to figure out if the messages are not received in the
|
|
|
|
* correct order (if multiple network interfaces).
|
|
|
|
*/
|
2007-12-19 12:16:20 +03:00
|
|
|
PERUSE_TRACE_MSG_EVENT(PERUSE_COMM_MSG_ARRIVED, comm_ptr,
|
2008-01-15 08:32:53 +03:00
|
|
|
hdr->hdr_src, hdr->hdr_tag, PERUSE_RECV);
|
2006-03-31 21:09:09 +04:00
|
|
|
|
2005-06-02 01:09:43 +04:00
|
|
|
/* get next expected message sequence number - if threaded
|
|
|
|
* run, lock to make sure that if another thread is processing
|
|
|
|
* a frag from the same message a match is made only once.
|
|
|
|
* Also, this prevents other posted receives (for a pair of
|
|
|
|
* end points) from being processed, and potentially "loosing"
|
|
|
|
* the fragment.
|
|
|
|
*/
|
2005-07-04 02:45:48 +04:00
|
|
|
OPAL_THREAD_LOCK(&comm->matching_lock);
|
2005-06-02 01:09:43 +04:00
|
|
|
|
|
|
|
/* get sequence number of next message that can be processed */
|
|
|
|
next_msg_seq_expected = (uint16_t)proc->expected_sequence;
|
2007-12-19 12:16:20 +03:00
|
|
|
if(OPAL_UNLIKELY(frag_msg_seq != next_msg_seq_expected))
|
|
|
|
goto wrong_seq;
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
/*
|
|
|
|
* This is the sequence number we were expecting,
|
|
|
|
* so we can try matching it to already posted
|
|
|
|
* receives.
|
|
|
|
*/
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
out_of_order_match:
|
|
|
|
/* We're now expecting the next sequence number. */
|
|
|
|
proc->expected_sequence++;
|
2006-03-31 21:09:09 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
/**
|
|
|
|
* We generate the SEARCH_POSTED_QUEUE only when the message is received
|
|
|
|
* in the correct sequence. Otherwise, we delay the event generation until
|
|
|
|
* we reach the correct sequence number.
|
|
|
|
*/
|
|
|
|
PERUSE_TRACE_MSG_EVENT(PERUSE_COMM_SEARCH_POSTED_Q_BEGIN, comm_ptr,
|
|
|
|
hdr->hdr_src, hdr->hdr_tag, PERUSE_RECV);
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
match = match_one(btl, hdr, segments, num_segments, comm_ptr, proc, frag);
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
/**
|
|
|
|
* The match is over. We generate the SEARCH_POSTED_Q_END here, before going
|
|
|
|
* into the mca_pml_ob1_check_cantmatch_for_match so we can make a difference
|
|
|
|
* for the searching time for all messages.
|
|
|
|
*/
|
|
|
|
PERUSE_TRACE_MSG_EVENT(PERUSE_COMM_SEARCH_POSTED_Q_END, comm_ptr,
|
|
|
|
hdr->hdr_src, hdr->hdr_tag, PERUSE_RECV);
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2005-12-11 02:25:29 +03:00
|
|
|
/* release matching lock before processing fragment */
|
2005-08-02 21:36:01 +04:00
|
|
|
OPAL_THREAD_UNLOCK(&comm->matching_lock);
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
if(OPAL_LIKELY(match)) {
|
2008-05-30 05:29:09 +04:00
|
|
|
switch(type) {
|
|
|
|
case MCA_PML_OB1_HDR_TYPE_MATCH:
|
|
|
|
mca_pml_ob1_recv_request_progress_match(match, btl, segments, num_segments);
|
|
|
|
break;
|
|
|
|
case MCA_PML_OB1_HDR_TYPE_RNDV:
|
|
|
|
mca_pml_ob1_recv_request_progress_rndv(match, btl, segments, num_segments);
|
|
|
|
break;
|
|
|
|
case MCA_PML_OB1_HDR_TYPE_RGET:
|
|
|
|
mca_pml_ob1_recv_request_progress_rget(match, btl, segments, num_segments);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
if(OPAL_UNLIKELY(frag))
|
2005-06-10 00:16:33 +04:00
|
|
|
MCA_PML_OB1_RECV_FRAG_RETURN(frag);
|
2005-06-02 01:09:43 +04:00
|
|
|
}
|
2008-05-30 05:29:09 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
/*
|
|
|
|
* Now that new message has arrived, check to see if
|
|
|
|
* any fragments on the c_c_frags_cant_match list
|
|
|
|
* may now be used to form new matchs
|
|
|
|
*/
|
|
|
|
if(OPAL_UNLIKELY(opal_list_get_size(&proc->frags_cant_match) > 0)) {
|
|
|
|
OPAL_THREAD_LOCK(&comm->matching_lock);
|
|
|
|
if((frag = check_cantmatch_for_match(proc))) {
|
|
|
|
hdr = &frag->hdr.hdr_match;
|
|
|
|
segments = frag->segments;
|
|
|
|
num_segments = frag->num_segments;
|
|
|
|
btl = frag->btl;
|
2008-05-30 05:29:09 +04:00
|
|
|
type = hdr->hdr_common.hdr_type;
|
2007-12-19 12:16:20 +03:00
|
|
|
goto out_of_order_match;
|
|
|
|
}
|
|
|
|
OPAL_THREAD_UNLOCK(&comm->matching_lock);
|
|
|
|
}
|
2005-06-02 01:09:43 +04:00
|
|
|
|
2007-12-19 12:16:20 +03:00
|
|
|
return OMPI_SUCCESS;
|
|
|
|
wrong_seq:
|
2005-06-02 01:09:43 +04:00
|
|
|
/*
|
2007-12-19 12:16:20 +03:00
|
|
|
* This message comes after the next expected, so it
|
|
|
|
* is ahead of sequence. Save it for later.
|
2005-06-02 01:09:43 +04:00
|
|
|
*/
|
2007-12-19 12:16:20 +03:00
|
|
|
append_frag_to_list(&proc->frags_cant_match, btl, hdr, segments,
|
2008-01-15 08:32:53 +03:00
|
|
|
num_segments, NULL);
|
2007-12-19 12:16:20 +03:00
|
|
|
OPAL_THREAD_UNLOCK(&comm->matching_lock);
|
|
|
|
return OMPI_SUCCESS;
|
2005-06-02 01:09:43 +04:00
|
|
|
}
|
2008-11-05 00:58:06 +03:00
|
|
|
|