mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Philipp Reisner <philipp.reisner@linbit.com>
To: linux-kernel@vger.kernel.org, Jens Axboe <axboe@kernel.dk>
Cc: drbd-dev@lists.linbit.com
Subject: [PATCH 053/118] drbd: Defer new writes when detecting conflicting writes
Date: Thu, 25 Aug 2011 17:07:49 +0200	[thread overview]
Message-ID: <1314284934-17999-54-git-send-email-philipp.reisner@linbit.com> (raw)
In-Reply-To: <1314284934-17999-1-git-send-email-philipp.reisner@linbit.com>

From: Andreas Gruenbacher <agruen@linbit.com>

Before submitting a new local write request, wait for any conflicting
local or remote requests to complete.

We could assume that the new request occurred first and that the
conflicting requests overwrote it (and therefore discard the new
reques), but we know for sure that the new request occurred after the
conflicting requests and so this behavior would we weird.  We would also
end up with the wrong result if the new request is not fully contained
within the conflicting requests.

Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
---
 drivers/block/drbd/drbd_req.c |  103 +++++++++++++++-------------------------
 1 files changed, 39 insertions(+), 64 deletions(-)

diff --git a/drivers/block/drbd/drbd_req.c b/drivers/block/drbd/drbd_req.c
index e11ea47..6bcf417 100644
--- a/drivers/block/drbd/drbd_req.c
+++ b/drivers/block/drbd/drbd_req.c
@@ -300,48 +300,6 @@ static void _req_may_be_done_not_susp(struct drbd_request *req, struct bio_and_e
 		_req_may_be_done(req, m);
 }
 
-/*
- * checks whether there was an overlapping request
- * or ee already registered.
- *
- * if so, return 1, in which case this request is completed on the spot,
- * without ever being submitted or send.
- *
- * return 0 if it is ok to submit this request.
- *
- * NOTE:
- * paranoia: assume something above us is broken, and issues different write
- * requests for the same block simultaneously...
- *
- * To ensure these won't be reordered differently on both nodes, resulting in
- * diverging data sets, we discard the later one(s). Not that this is supposed
- * to happen, but this is the rationale why we also have to check for
- * conflicting requests with local origin, and why we have to do so regardless
- * of whether we allowed multiple primaries.
- */
-static int _req_conflicts(struct drbd_request *req)
-{
-	struct drbd_conf *mdev = req->mdev;
-	const sector_t sector = req->i.sector;
-	const int size = req->i.size;
-	struct drbd_interval *i;
-
-	D_ASSERT(drbd_interval_empty(&req->i));
-
-	i = drbd_find_overlap(&mdev->write_requests, sector, size);
-	if (i) {
-		dev_alert(DEV, "%s[%u] Concurrent %s write detected! "
-		      "[DISCARD L] new: %llus +%u; "
-		      "pending: %llus +%u\n",
-		      current->comm, current->pid,
-		      i->local ? "local" : "remote",
-		      (unsigned long long)sector, size,
-		      (unsigned long long)i->sector, i->size);
-		return 1;
-	}
-	return 0;
-}
-
 /* obviously this could be coded as many single functions
  * instead of one huge switch,
  * or by putting the code directly in the respective locations
@@ -721,6 +679,34 @@ static int drbd_may_do_local_read(struct drbd_conf *mdev, sector_t sector, int s
 	return 0 == drbd_bm_count_bits(mdev, sbnr, ebnr);
 }
 
+/*
+ * complete_conflicting_writes  -  wait for any conflicting write requests
+ *
+ * The write_requests tree contains all active write requests which we
+ * currently know about.  Wait for any requests to complete which conflict with
+ * the new one.
+ */
+static int complete_conflicting_writes(struct drbd_conf *mdev,
+				       sector_t sector, int size)
+{
+	for(;;) {
+		DEFINE_WAIT(wait);
+		struct drbd_interval *i;
+
+		i = drbd_find_overlap(&mdev->write_requests, sector, size);
+		if (!i)
+			return 0;
+		i->waiting = true;
+		prepare_to_wait(&mdev->misc_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_irq(&mdev->tconn->req_lock);
+		schedule();
+		finish_wait(&mdev->misc_wait, &wait);
+		spin_lock_irq(&mdev->tconn->req_lock);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+	}
+}
+
 static int drbd_make_request_common(struct drbd_conf *mdev, struct bio *bio, unsigned long start_time)
 {
 	const int rw = bio_rw(bio);
@@ -729,7 +715,7 @@ static int drbd_make_request_common(struct drbd_conf *mdev, struct bio *bio, uns
 	struct drbd_tl_epoch *b = NULL;
 	struct drbd_request *req;
 	int local, remote, send_oos = 0;
-	int err = -EIO;
+	int err;
 	int ret = 0;
 
 	/* allocate outside of all locks; */
@@ -799,6 +785,7 @@ static int drbd_make_request_common(struct drbd_conf *mdev, struct bio *bio, uns
 	if (!(local || remote) && !is_susp(mdev->state)) {
 		if (__ratelimit(&drbd_ratelimit_state))
 			dev_err(DEV, "IO ERROR: neither local nor remote disk\n");
+		err = -EIO;
 		goto fail_free_complete;
 	}
 
@@ -823,6 +810,14 @@ allocate_barrier:
 	/* GOOD, everything prepared, grab the spin_lock */
 	spin_lock_irq(&mdev->tconn->req_lock);
 
+	if (rw == WRITE) {
+		err = complete_conflicting_writes(mdev, sector, size);
+		if (err) {
+			spin_unlock_irq(&mdev->tconn->req_lock);
+			goto fail_free_complete;
+		}
+	}
+
 	if (is_susp(mdev->state)) {
 		/* If we got suspended, use the retry mechanism of
 		   generic_make_request() to restart processing of this
@@ -843,6 +838,7 @@ allocate_barrier:
 		if (!(local || remote)) {
 			dev_err(DEV, "IO ERROR: neither local nor remote disk\n");
 			spin_unlock_irq(&mdev->tconn->req_lock);
+			err = -EIO;
 			goto fail_free_complete;
 		}
 	}
@@ -903,12 +899,6 @@ allocate_barrier:
 	if (local)
 		_req_mod(req, TO_BE_SUBMITTED);
 
-	/* check this request on the collision detection hash tables.
-	 * if we have a conflict, just complete it here.
-	 * THINK do we want to check reads, too? (I don't think so...) */
-	if (rw == WRITE && _req_conflicts(req))
-		goto fail_conflicting;
-
 	list_add_tail(&req->tl_requests, &mdev->tconn->newest_tle->requests);
 
 	/* NOTE remote first: to get the concurrent write detection right,
@@ -975,21 +965,6 @@ allocate_barrier:
 
 	return 0;
 
-fail_conflicting:
-	/* this is a conflicting request.
-	 * even though it may have been only _partially_
-	 * overlapping with one of the currently pending requests,
-	 * without even submitting or sending it, we will
-	 * pretend that it was successfully served right now.
-	 */
-	_drbd_end_io_acct(mdev, req);
-	spin_unlock_irq(&mdev->tconn->req_lock);
-	if (remote)
-		dec_ap_pending(mdev);
-	/* THINK: do we want to fail it (-EIO), or pretend success?
-	 * this pretends success. */
-	err = 0;
-
 fail_free_complete:
 	if (req->rq_state & RQ_IN_ACT_LOG)
 		drbd_al_complete_io(mdev, sector);
-- 
1.7.4.1


  parent reply	other threads:[~2011-08-25 15:26 UTC|newest]

Thread overview: 132+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-08-25 15:06 [RFC 000/118] drbd: part 1 of adding multiple volume support to drbd Philipp Reisner
2011-08-25 15:06 ` [PATCH 001/118] drbd: Get rid of req_validator_fn typedef Philipp Reisner
2011-08-25 15:06 ` [PATCH 002/118] drbd: Remove superfluous declaration Philipp Reisner
2011-08-25 15:06 ` [PATCH 003/118] drbd: Consistently use block_id == ID_SYNCER for checksum based resync and online verify Philipp Reisner
2011-08-25 15:07 ` [PATCH 004/118] drbd: Get rid of BE_DRBD_MAGIC and BE_DRBD_MAGIC_BIG Philipp Reisner
2011-08-25 15:07 ` [PATCH 005/118] drbd: Endianness convert the constants instead of the variables Philipp Reisner
2011-08-25 15:07 ` [PATCH 006/118] drbd: Magic reserved block_id value cleanup Philipp Reisner
2011-08-25 15:07 ` [PATCH 007/118] drbd: Move drbd_free_tl_hash() to drbd_main() Philipp Reisner
2011-08-25 15:07 ` [PATCH 008/118] drbd: Update outdated comment Philipp Reisner
2011-08-25 15:07 ` [PATCH 009/118] drbd: Request lookup code cleanup (1) Philipp Reisner
2011-08-25 15:07 ` [PATCH 010/118] drbd: Request lookup code cleanup (2) Philipp Reisner
2011-08-25 15:07 ` [PATCH 011/118] drbd: Request lookup code cleanup (3) Philipp Reisner
2011-08-25 15:07 ` [PATCH 012/118] drbd: Request lookup code cleanup (4) Philipp Reisner
2011-08-25 15:07 ` [PATCH 013/118] drbd: Add interval tree data structure Philipp Reisner
2011-08-25 15:07 ` [PATCH 014/118] drbd: Put sector and size in struct drbd_request into struct drbd_interval Philipp Reisner
2011-08-25 15:07 ` [PATCH 015/118] drbd: Use interval tree for overlapping write request detection Philipp Reisner
2011-08-25 15:07 ` [PATCH 016/118] drbd: Add read_requests tree Philipp Reisner
2011-08-25 18:02   ` [Drbd-dev] " Pawel Jakub Dawidek
2011-08-29 11:42     ` Philipp Reisner
2011-08-25 15:07 ` [PATCH 017/118] drbd: Use the read and write request trees for request lookups Philipp Reisner
2011-08-25 15:07 ` [PATCH 018/118] drbd: Put sector and size in struct drbd_epoch_entry into struct drbd_interval Philipp Reisner
2011-08-25 15:07 ` [PATCH 019/118] drbd: Use interval tree for overlapping epoch entry detection Philipp Reisner
2011-08-25 15:07 ` [PATCH 020/118] drbd: Remove the unused hash tables Philipp Reisner
2011-08-25 15:07 ` [PATCH 021/118] drbd: Convert all constants in enum drbd_req_event to upper case Philipp Reisner
2011-08-25 15:07 ` [PATCH 022/118] drbd: Convert all constants in enum drbd_thread_state " Philipp Reisner
2011-08-25 15:07 ` [PATCH 023/118] drbd: Replace the ERR_IF macro with an assert-like macro Philipp Reisner
2011-08-25 15:07 ` [PATCH 024/118] drbd: Remove some useless paranoia code Philipp Reisner
2011-08-25 15:07 ` [PATCH 025/118] drbd: Inline function overlaps() is now unused Philipp Reisner
2011-08-25 15:07 ` [PATCH 026/118] drbd: Interval tree bugfix Philipp Reisner
2011-08-25 15:07 ` [PATCH 027/118] idr: idr_for_each_entry() macro Philipp Reisner
2011-08-25 15:07 ` [PATCH 028/118] drbd: Minimal struct drbd_tconn Philipp Reisner
2011-08-25 15:07 ` [PATCH 029/118] drbd: moved net_conf from mdev to tconn Philipp Reisner
2011-08-25 15:07 ` [PATCH 030/118] drbd: moved net_cont and net_cnt_wait " Philipp Reisner
2011-08-25 15:07 ` [PATCH 031/118] drbd: moved data and meta " Philipp Reisner
2011-08-25 15:07 ` [PATCH 032/118] drbd: moved receiver, worker and asender " Philipp Reisner
2011-08-25 15:07 ` [PATCH 033/118] drbd: moved agreed_pro_version, last_received and ko_count " Philipp Reisner
2011-08-25 15:07 ` [PATCH 034/118] drbd: moved req_lock and transfer log from mdev " Philipp Reisner
2011-08-25 15:07 ` [PATCH 035/118] drbd: moved crypto transformations and friends " Philipp Reisner
2011-08-25 15:07 ` [PATCH 036/118] drbd: Made drbd_flush_workqueue() to take a tconn instead of an mdev Philipp Reisner
2011-08-25 15:07 ` [PATCH 037/118] drbd: Preparing to use p_header96 for all packets Philipp Reisner
2011-08-25 15:07 ` [PATCH 038/118] drbd: Replaced all p_header80 with a generic p_header Philipp Reisner
2011-08-25 15:07 ` [PATCH 039/118] drbd: Use new header layout, and send volume IOs Philipp Reisner
2011-08-25 15:07 ` [PATCH 040/118] drbd: Implemented receiving of new style packets on meta socket Philipp Reisner
2011-08-25 15:07 ` [PATCH 041/118] drbd: Do not access tconn after it was freed Philipp Reisner
2011-08-25 15:07 ` [PATCH 042/118] drbd: Move cmdname() out of drbd_int.h Philipp Reisner
2011-08-25 15:07 ` [PATCH 043/118] drbd: Rename "enum drbd_packets" to "enum drbd_packet" Philipp Reisner
2011-08-25 15:07 ` [PATCH 044/118] drbd: Remove redundant initialization Philipp Reisner
2011-08-25 15:07 ` [PATCH 045/118] drbd: Initialize the sequence number sent over the network even when not used Philipp Reisner
2011-08-25 15:07 ` [PATCH 046/118] drbd: Move sequence number logic into drbd_receiver.c and simplify it Philipp Reisner
2011-08-25 15:07 ` [PATCH 047/118] drbd: Move some functions to where they are used Philipp Reisner
2011-08-25 15:07 ` [PATCH 048/118] drbd: struct drbd_request: Introduce a new collision flag Philipp Reisner
2011-08-25 15:07 ` [PATCH 049/118] drbd: Remove redundant check from drbd_contains_interval() Philipp Reisner
2011-08-25 15:07 ` [PATCH 050/118] drbd: Allow to wait for the completion of an epoch entry as well Philipp Reisner
2011-08-25 15:07 ` [PATCH 051/118] drbd: _req_conflicts(): Get rid of the epoch_entries tree Philipp Reisner
2011-08-25 15:07 ` [PATCH 052/118] drbd: Remove unnecessary reference counting left-over Philipp Reisner
2011-08-25 15:07 ` Philipp Reisner [this message]
2011-08-25 15:07 ` [PATCH 054/118] drbd: Make the peer_seq updating code more obvious Philipp Reisner
2011-08-25 15:07 ` [PATCH 055/118] drbd: Improve the drbd_find_overlap() documentation Philipp Reisner
2011-08-25 15:07 ` [PATCH 056/118] drbd: Remove unused variable in struct drbd_conf Philipp Reisner
2011-08-25 15:07 ` [PATCH 057/118] drbd: Rename struct drbd_epoch_entry to struct drbd_peer_request Philipp Reisner
2011-08-25 15:07 ` [PATCH 058/118] drbd: Clean up some left-overs Philipp Reisner
2011-08-25 15:07 ` [PATCH 059/118] drbd: Update some comments Philipp Reisner
2011-08-25 15:07 ` [PATCH 060/118] drbd: Local variable renames: e -> peer_req Philipp Reisner
2011-08-25 15:07 ` [PATCH 061/118] drbd: Moved the state functions into its own source file Philipp Reisner
2011-08-25 15:07 ` [PATCH 062/118] drbd: Moved the thread name into the data structure Philipp Reisner
2011-08-25 15:07 ` [PATCH 063/118] drbd: Eliminated the user of drbd_task_to_thread() Philipp Reisner
2011-08-25 15:08 ` [PATCH 064/118] drbd: Moved code Philipp Reisner
2011-08-25 15:08 ` [PATCH 065/118] drbd: Do no sleep long in drbd_start_resync Philipp Reisner
2011-08-25 15:08 ` [PATCH 066/118] drbd: Revert "Make sure we dont send state if a cluster wide state change is in progress" Philipp Reisner
2011-08-25 15:08 ` [PATCH 067/118] drbd: Moving state related macros to drbd_state.h Philipp Reisner
2011-08-25 15:08 ` [PATCH 068/118] drbd: conn_printk() a dev_printk() alike for drbd's connections Philipp Reisner
2011-08-25 18:16   ` Joe Perches
2011-08-29 11:42     ` [Drbd-dev] " Philipp Reisner
2011-08-25 15:08 ` [PATCH 069/118] drbd: Converted drbd_try_connect() from mdev to tconn Philipp Reisner
2011-08-25 15:08 ` [PATCH 070/118] drbd: Converted drbd_wait_for_connect() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 071/118] drbd: Started to separated connection flags (tconn) from block device flags (mdev) Philipp Reisner
2011-08-25 15:08 ` [PATCH 072/118] drbd: Moved DISCARD_CONCURRENT to the per connection (tconn) flags Philipp Reisner
2011-08-25 15:08 ` [PATCH 073/118] drbd: Moved SEND_PING " Philipp Reisner
2011-08-25 15:08 ` [PATCH 074/118] drbd: Moved SIGNAL_ASENDER " Philipp Reisner
2011-08-25 15:08 ` [PATCH 075/118] drbd: Converted wake_asender() and request_ping() from mdev to tconn Philipp Reisner
2011-08-25 15:08 ` [PATCH 076/118] drbd: Converted helper functions for drbd_send() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 077/118] drbd: Converted drbd_send() from mdev " Philipp Reisner
2011-08-25 15:08 ` [PATCH 078/118] drbd: Converted drbd_send_fp() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 079/118] drbd: Removed unused mdev argument from drbd_recv_short() and drbd_socket_okay() Philipp Reisner
2011-08-25 15:08 ` [PATCH 080/118] drbd: Converted drbd_recv_fp() from mdev to tconn Philipp Reisner
2011-08-25 15:08 ` [PATCH 081/118] drbd: Converted drbd_send_handshake() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 082/118] drbd: Converted drbd_recv() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 083/118] drbd: struct packet_info to hold information of decoded packets Philipp Reisner
2011-08-25 15:08 ` [PATCH 084/118] drbd: Converted decode_header() from mdev to tconn Philipp Reisner
2011-08-25 15:08 ` [PATCH 085/118] drbd: Converted drbd_recv_header() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 086/118] drbd: Converted drbd_do_handshake() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 087/118] drbd: Converted drbd_(get|put)_data_sock() and drbd_send_cmd2() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 088/118] drbd: Converted drbd_do_auth() from mdev " Philipp Reisner
2011-08-25 15:08 ` [PATCH 089/118] drbd: Converted drbd_send_protocol() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 090/118] drbd: Use and idr data structure to map volume numbers to mdev pointers Philipp Reisner
2011-08-25 15:08 ` [PATCH 091/118] drbd: Converted drbd_connect() from mdev to tconn Philipp Reisner
2011-08-25 15:08 ` [PATCH 092/118] drbd: Converted drbd_calc_cpu_mask() and drbd_thread_current_set_cpu() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 093/118] drbd: Converted drbdd() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 094/118] drbd: Converted drbd_free_sock() and drbd_disconnect() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 095/118] drbd: Moved the mdev member into drbd_work (from drbd_request and drbd_peer_request) Philipp Reisner
2011-08-25 15:08 ` [PATCH 096/118] drbd: Consolidated the setup of the thread name into the framework Philipp Reisner
2011-08-25 15:08 ` [PATCH 097/118] drbd: Converted drbdd_init() from mdev to tconn Philipp Reisner
2011-08-25 15:08 ` [PATCH 098/118] drbd: Converted drbd_asender() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 099/118] drbd: Converted drbd_worker() " Philipp Reisner
2011-08-25 15:08 ` [PATCH 100/118] drbd: drbd_thread has now a pointer to a tconn instead of to a mdev Philipp Reisner
2011-08-25 15:08 ` [PATCH 101/118] drbd: Moved some initializing code into drbd_new_tconn() Philipp Reisner
2011-08-25 15:08 ` [PATCH 102/118] drbd: Generalized the work callbacks Philipp Reisner
2011-08-25 15:08 ` [PATCH 103/118] drbd: Converted drbd_send_ping() and related functions from mdev to tconn Philipp Reisner
2011-08-25 15:08 ` [PATCH 104/118] drbd: Extracted after_conn_state_ch() out of after_state_ch() Philipp Reisner
2011-08-25 15:08 ` [PATCH 105/118] drbd: Renamed is_valid_state_transition() to is_valid_soft_transition() Philipp Reisner
2011-08-25 15:08 ` [PATCH 106/118] drbd: Extracted is_valid_transition() out of sanitize_state() Philipp Reisner
2011-08-25 15:08 ` [PATCH 107/118] drbd: Extracted is_valid_conn_transition() out of is_valid_transition() Philipp Reisner
2011-08-25 15:08 ` [PATCH 108/118] drbd: Removed the os parameter form sanitize_state() Philipp Reisner
2011-08-25 15:08 ` [PATCH 109/118] drbd: Code de-duplication; new function apply_mask_val() Philipp Reisner
2011-08-25 15:08 ` [PATCH 110/118] drbd: Killed volume0; last step of multi-volume-enablement Philipp Reisner
2011-08-25 15:08 ` [PATCH 111/118] drbd: Removed drbd_state_lock() and drbd_state_unlock() Philipp Reisner
2011-08-25 15:08 ` [PATCH 112/118] drbd: Introduced tconn->cstate_mutex Philipp Reisner
2011-08-25 15:08 ` [PATCH 113/118] drbd: Implemented conn_send_state_req() Philipp Reisner
2011-08-25 15:08 ` [PATCH 114/118] drbd: Global_state_lock not necessary here Philipp Reisner
2011-08-25 15:08 ` [PATCH 115/118] drbd: Implemented conn_send_sr_reply() Philipp Reisner
2011-08-25 15:08 ` [PATCH 116/118] drbd: Implemented receiving of P_CONN_ST_CHG_REPLY Philipp Reisner
2011-08-25 15:08 ` [PATCH 117/118] drbd: implemented receiving of P_CONN_ST_CHG_REQ Philipp Reisner
2011-08-25 15:08 ` [PATCH 118/118] drbd: Implemented connection wide state changes Philipp Reisner
2011-08-26  1:30 ` [RFC 000/118] drbd: part 1 of adding multiple volume support to drbd David Miller
     [not found] ` <CAGpXXZKem-uEdF4SyUgpQFV6=d5Vo3v9p3wmcaygAKa4w4x01w@mail.gmail.com>
2011-08-26 13:54   ` [Drbd-dev] " Lars Ellenberg
2011-08-26 15:18     ` Kyle Moffett
2011-08-29 11:51       ` Philipp Reisner
2011-08-29 12:52         ` Pekka Enberg
2011-08-29 15:38           ` Philipp Reisner
2011-08-29 11:46   ` Philipp Reisner
2011-08-29 13:26     ` Greg Freemyer
2011-08-29 16:07       ` Philipp Reisner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1314284934-17999-54-git-send-email-philipp.reisner@linbit.com \
    --to=philipp.reisner@linbit.com \
    --cc=axboe@kernel.dk \
    --cc=drbd-dev@lists.linbit.com \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®